diff --git a/.buildkite/pipelines/security_solution/api_integration.yml b/.buildkite/pipelines/security_solution/api_integration.yml index 8e36fbfa2ae4a..5b3394c05ffc7 100644 --- a/.buildkite/pipelines/security_solution/api_integration.yml +++ b/.buildkite/pipelines/security_solution/api_integration.yml @@ -1,319 +1,323 @@ steps: - - label: Running exception_workflows:qa:serverless - command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh exception_workflows:qa:serverless - key: exception_workflows:qa:serverless + - command: .buildkite/scripts/pipelines/security_solution_quality_gate/create_periodic_test_docker_image.sh + label: Build kibana image + key: build_image agents: - queue: n2-4-spot - timeout_in_minutes: 120 + queue: n2-16-spot + timeout_in_minutes: 60 retry: automatic: - - exit_status: '*' - limit: 2 + - exit_status: "-1" + limit: 3 - - label: Running exception_operators_date_numeric_types:qa:serverless - command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh exception_operators_date_numeric_types:qa:serverless - key: exception_operators_date_numeric_types:qa:serverless + - command: .buildkite/scripts/pipelines/security_solution_quality_gate/upload_image_metadata.sh + label: "Upload runtime info" + key: upload_runtime_info + depends_on: build_image agents: queue: n2-4-spot - timeout_in_minutes: 120 + timeout_in_minutes: 300 retry: automatic: - - exit_status: '*' + - exit_status: "-1" limit: 2 - - label: Running exception_operators_keyword:qa:serverless - command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh exception_operators_keyword:qa:serverless - key: exception_operators_keyword:qa:serverless - agents: - queue: n2-4-spot - timeout_in_minutes: 120 - retry: - automatic: - - exit_status: '*' - limit: 2 + - group: "Execute Tests" + depends_on: build_image + steps: + - label: Running exception_workflows:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh exception_workflows:qa:serverless + key: exception_workflows:qa:serverless + agents: + queue: n2-4-spot + timeout_in_minutes: 120 + retry: + automatic: + - exit_status: "*" + limit: 2 - - label: Running exception_operators_ips:qa:serverless - command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh exception_operators_ips:qa:serverless - key: exception_operators_ips:qa:serverless - agents: - queue: n2-4-spot - timeout_in_minutes: 120 - retry: - automatic: - - exit_status: '*' - limit: 2 + - label: Running exception_operators_date_numeric_types:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh exception_operators_date_numeric_types:qa:serverless + key: exception_operators_date_numeric_types:qa:serverless + agents: + queue: n2-4-spot + timeout_in_minutes: 120 + retry: + automatic: + - exit_status: "*" + limit: 2 - - label: Running exception_operators_long:qa:serverless - command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh exception_operators_long:qa:serverless - key: exception_operators_long:qa:serverless - agents: - queue: n2-4-spot - timeout_in_minutes: 120 - retry: - automatic: - - exit_status: '1' - limit: 2 + - label: Running exception_operators_keyword:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh exception_operators_keyword:qa:serverless + key: exception_operators_keyword:qa:serverless + agents: + queue: n2-4-spot + timeout_in_minutes: 120 + retry: + automatic: + - exit_status: "*" + limit: 2 - - label: Running exception_operators_text:qa:serverless - command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh exception_operators_text:qa:serverless - key: exception_operators_text:qa:serverless - agents: - queue: n2-4-spot - timeout_in_minutes: 120 - retry: - automatic: - - exit_status: '1' - limit: 2 + - label: Running exception_operators_ips:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh exception_operators_ips:qa:serverless + key: exception_operators_ips:qa:serverless + agents: + queue: n2-4-spot + timeout_in_minutes: 120 + retry: + automatic: + - exit_status: "*" + limit: 2 - - label: Running rule_creation:qa:serverless - command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh rule_creation:qa:serverless - key: rule_creation:qa:serverless - agents: - queue: n2-4-spot - timeout_in_minutes: 120 - retry: - automatic: - - exit_status: '1' - limit: 2 + - label: Running exception_operators_long:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh exception_operators_long:qa:serverless + key: exception_operators_long:qa:serverless + agents: + queue: n2-4-spot + timeout_in_minutes: 120 + retry: + automatic: + - exit_status: "1" + limit: 2 - - label: Running rule_creation:essentials:qa:serverless - command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh rule_creation:essentials:qa:serverless - key: rule_creation:essentials:qa:serverless - agents: - queue: n2-4-spot - timeout_in_minutes: 120 - retry: - automatic: - - exit_status: '1' - limit: 2 + - label: Running exception_operators_text:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh exception_operators_text:qa:serverless + key: exception_operators_text:qa:serverless + agents: + queue: n2-4-spot + timeout_in_minutes: 120 + retry: + automatic: + - exit_status: "1" + limit: 2 - - label: Running alerts:qa:serverless - command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh alerts:qa:serverless - key: alerts:qa:serverless - agents: - queue: n2-4-spot - timeout_in_minutes: 120 - retry: - automatic: - - exit_status: '1' - limit: 2 + - label: Running rule_creation:essentials:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh rule_creation:essentials:qa:serverless + key: rule_creation:essentials:qa:serverless + agents: + queue: n2-4-spot + timeout_in_minutes: 120 + retry: + automatic: + - exit_status: '1' + limit: 2 - - label: Running alerts:essentials:qa:serverless - command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh alerts:essentials:qa:serverless - key: alerts:essentials:qa:serverless - agents: - queue: n2-4-spot - timeout_in_minutes: 120 - retry: - automatic: - - exit_status: '1' - limit: 2 + - label: Running alerts:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh alerts:qa:serverless + key: alerts:qa:serverless + agents: + queue: n2-4-spot + timeout_in_minutes: 120 + retry: + automatic: + - exit_status: '1' + limit: 2 - - label: Running actions:qa:serverless - command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh actions:qa:serverless - key: actions:qa:serverless - agents: - queue: n2-4-spot - timeout_in_minutes: 120 - retry: - automatic: - - exit_status: '1' - limit: 2 + - label: Running alerts:essentials:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh alerts:essentials:qa:serverless + key: alerts:essentials:qa:serverless + agents: + queue: n2-4-spot + timeout_in_minutes: 120 + retry: + automatic: + - exit_status: '1' + limit: 2 - - label: Running entity_analytics:qa:serverless - command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh entity_analytics:qa:serverless - key: entity_analytics:qa:serverless - agents: - queue: n2-4-spot - timeout_in_minutes: 120 - retry: - automatic: - - exit_status: '1' - limit: 2 + - label: Running actions:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh actions:qa:serverless + key: actions:qa:serverless + agents: + queue: n2-4-spot + timeout_in_minutes: 120 + retry: + automatic: + - exit_status: '1' + limit: 2 - - label: Running prebuilt_rules_management:qa:serverless - command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh prebuilt_rules_management:qa:serverless - key: prebuilt_rules_management:qa:serverless - agents: - queue: n2-4-spot - timeout_in_minutes: 120 - retry: - automatic: - - exit_status: '1' - limit: 2 + - label: Running entity_analytics:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh entity_analytics:qa:serverless + key: entity_analytics:qa:serverless + agents: + queue: n2-4-spot + timeout_in_minutes: 120 + retry: + automatic: + - exit_status: "1" + limit: 2 - - label: Running prebuilt_rules_bundled_prebuilt_rules_package:qa:serverless - command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh prebuilt_rules_bundled_prebuilt_rules_package:qa:serverless - key: prebuilt_rules_bundled_prebuilt_rules_package:qa:serverless - agents: - queue: n2-4-spot - timeout_in_minutes: 120 - retry: - automatic: - - exit_status: '1' - limit: 2 + - label: Running prebuilt_rules_management:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh prebuilt_rules_management:qa:serverless + key: prebuilt_rules_management:qa:serverless + agents: + queue: n2-4-spot + timeout_in_minutes: 120 + retry: + automatic: + - exit_status: "1" + limit: 2 - - label: Running prebuilt_rules_large_prebuilt_rules_package:qa:serverless - command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh prebuilt_rules_large_prebuilt_rules_package:qa:serverless - key: prebuilt_rules_large_prebuilt_rules_package:qa:serverless - agents: - queue: n2-4-spot - timeout_in_minutes: 120 - retry: - automatic: - - exit_status: '1' - limit: 2 + - label: Running prebuilt_rules_bundled_prebuilt_rules_package:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh prebuilt_rules_bundled_prebuilt_rules_package:qa:serverless + key: prebuilt_rules_bundled_prebuilt_rules_package:qa:serverless + agents: + queue: n2-4-spot + timeout_in_minutes: 120 + retry: + automatic: + - exit_status: "1" + limit: 2 - - label: Running prebuilt_rules_update_prebuilt_rules_package:qa:serverless - command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh prebuilt_rules_update_prebuilt_rules_package:qa:serverless - key: prebuilt_rules_update_prebuilt_rules_package:qa:serverless - agents: - queue: n2-4-spot - timeout_in_minutes: 120 - retry: - automatic: - - exit_status: '1' - limit: 2 + - label: Running prebuilt_rules_large_prebuilt_rules_package:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh prebuilt_rules_large_prebuilt_rules_package:qa:serverless + key: prebuilt_rules_large_prebuilt_rules_package:qa:serverless + agents: + queue: n2-4-spot + timeout_in_minutes: 120 + retry: + automatic: + - exit_status: "1" + limit: 2 - - label: Running rule_execution_logic:qa:serverless - command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh rule_execution_logic:qa:serverless - key: rule_execution_logic:qa:serverless - agents: - queue: n2-4-spot - timeout_in_minutes: 120 - retry: - automatic: - - exit_status: '1' - limit: 2 + - label: Running prebuilt_rules_update_prebuilt_rules_package:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh prebuilt_rules_update_prebuilt_rules_package:qa:serverless + key: prebuilt_rules_update_prebuilt_rules_package:qa:serverless + agents: + queue: n2-4-spot + timeout_in_minutes: 120 + retry: + automatic: + - exit_status: "1" + limit: 2 - - label: Running user_roles:qa:serverless - command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh user_roles:qa:serverless - key: user_roles:qa:serverless - agents: - queue: n2-4-spot - timeout_in_minutes: 120 - retry: - automatic: - - exit_status: '1' - limit: 2 + - label: Running rule_execution_logic:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh rule_execution_logic:qa:serverless + key: rule_execution_logic:qa:serverless + agents: + queue: n2-4-spot + timeout_in_minutes: 120 + retry: + automatic: + - exit_status: "1" + limit: 2 - - label: Running telemetry:qa:serverless - command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh telemetry:qa:serverless - key: telemetry:qa:serverless - agents: - queue: n2-4-spot - timeout_in_minutes: 120 - retry: - automatic: - - exit_status: '1' - limit: 2 + - label: Running user_roles:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh user_roles:qa:serverless + key: user_roles:qa:serverless + agents: + queue: n2-4-spot + timeout_in_minutes: 120 + retry: + automatic: + - exit_status: "1" + limit: 2 - - label: Running rule_delete:qa:serverless - command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh rule_delete:qa:serverless - key: rule_delete:qa:serverless - agents: - queue: n2-4-spot - timeout_in_minutes: 120 - retry: - automatic: - - exit_status: '1' - limit: 2 + - label: Running telemetry:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh telemetry:qa:serverless + key: telemetry:qa:serverless + agents: + queue: n2-4-spot + timeout_in_minutes: 120 + retry: + automatic: + - exit_status: "1" + limit: 2 - - label: Running rule_update:qa:serverless - command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh rule_update:qa:serverless - key: rule_update:qa:serverless - agents: - queue: n2-4-spot - timeout_in_minutes: 120 - retry: - automatic: - - exit_status: '1' - limit: 2 + - label: Running rule_delete:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh rule_delete:qa:serverless + key: rule_delete:qa:serverless + agents: + queue: n2-4-spot + timeout_in_minutes: 120 + retry: + automatic: + - exit_status: "1" + limit: 2 - - label: Running rule_patch:qa:serverless - command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh rule_patch:qa:serverless - key: rule_patch:qa:serverless - agents: - queue: n2-4-spot - timeout_in_minutes: 120 - retry: - automatic: - - exit_status: '1' - limit: 2 + - label: Running rule_update:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh rule_update:qa:serverless + key: rule_update:qa:serverless + agents: + queue: n2-4-spot + timeout_in_minutes: 120 + retry: + automatic: + - exit_status: "1" + limit: 2 - - label: Running rule_import_export:qa:serverless - command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh rule_import_export:qa:serverless - key: rule_import_export:qa:serverless - agents: - queue: n2-4-spot - timeout_in_minutes: 120 - retry: - automatic: - - exit_status: '1' - limit: 2 + - label: Running rule_patch:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh rule_patch:qa:serverless + key: rule_patch:qa:serverless + agents: + queue: n2-4-spot + timeout_in_minutes: 120 + retry: + automatic: + - exit_status: "1" + limit: 2 - - label: Running rule_management:qa:serverless - command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh rule_management:qa:serverless - key: rule_management:qa:serverless - agents: - queue: n2-4-spot - timeout_in_minutes: 120 - retry: - automatic: - - exit_status: '1' - limit: 2 + - label: Running rule_import_export:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh rule_import_export:qa:serverless + key: rule_import_export:qa:serverless + agents: + queue: n2-4-spot + timeout_in_minutes: 120 + retry: + automatic: + - exit_status: "1" + limit: 2 - - label: Running rule_bulk_actions:qa:serverless - command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh rule_bulk_actions:qa:serverless - key: rule_bulk_actions:qa:serverless - agents: - queue: n2-4-spot - timeout_in_minutes: 120 - retry: - automatic: - - exit_status: '1' - limit: 2 + - label: Running rule_management:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh rule_management:qa:serverless + key: rule_management:qa:serverless + agents: + queue: n2-4-spot + timeout_in_minutes: 120 + retry: + automatic: + - exit_status: "1" + limit: 2 - - label: Running rule_read:qa:serverless - command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh rule_read:qa:serverless - key: rule_read:qa:serverless - agents: - queue: n2-4-spot - timeout_in_minutes: 120 - retry: - automatic: - - exit_status: '1' - limit: 2 + - label: Running rule_read:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh rule_read:qa:serverless + key: rule_read:qa:serverless + agents: + queue: n2-4-spot + timeout_in_minutes: 120 + retry: + automatic: + - exit_status: '1' + limit: 2 - - label: Running rules_management:essentials:qa:serverless - command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh rules_management:essentials:qa:serverless - key: rules_management:essentials:qa:serverless - agents: - queue: n2-4-spot - timeout_in_minutes: 120 - retry: - automatic: - - exit_status: '1' - limit: 2 + - label: Running rules_management:essentials:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh rules_management:essentials:qa:serverless + key: rules_management:essentials:qa:serverless + agents: + queue: n2-4-spot + timeout_in_minutes: 120 + retry: + automatic: + - exit_status: '1' + limit: 2 - - label: Running exception_lists_items:qa:serverless - command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh exception_lists_items:qa:serverless - key: exception_lists_items:qa:serverless - agents: - queue: n2-4-spot - timeout_in_minutes: 120 - retry: - automatic: - - exit_status: '1' - limit: 2 + - label: Running exception_lists_items:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh exception_lists_items:qa:serverless + key: exception_lists_items:qa:serverless + agents: + queue: n2-4-spot + timeout_in_minutes: 120 + retry: + automatic: + - exit_status: '1' + limit: 2 - - label: Running lists_items:qa:serverless - command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh lists_items:qa:serverless - key: lists_items:qa:serverless - agents: - queue: n2-4-spot - timeout_in_minutes: 120 - retry: - automatic: - - exit_status: '1' - limit: 2 + - label: Running lists_items:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh lists_items:qa:serverless + key: lists_items:qa:serverless + agents: + queue: n2-4-spot + timeout_in_minutes: 120 + retry: + automatic: + - exit_status: '1' + limit: 2 diff --git a/.buildkite/pipelines/security_solution/security_solution_cypress.yml b/.buildkite/pipelines/security_solution/security_solution_cypress.yml index f6fce93c56614..997e607ebb43f 100644 --- a/.buildkite/pipelines/security_solution/security_solution_cypress.yml +++ b/.buildkite/pipelines/security_solution/security_solution_cypress.yml @@ -1,96 +1,122 @@ steps: - - command: .buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh cypress:run:qa:serverless:explore - label: 'Serverless MKI QA Explore - Security Solution Cypress Tests' + - command: .buildkite/scripts/pipelines/security_solution_quality_gate/create_periodic_test_docker_image.sh + label: Build kibana image + key: build_image agents: - queue: n2-4-spot - # TODO : Revise the timeout when the pipeline will be officially integrated with the quality gate. - timeout_in_minutes: 300 - parallelism: 4 + queue: n2-16-spot + timeout_in_minutes: 60 retry: automatic: - - exit_status: '*' - limit: 1 + - exit_status: "-1" + limit: 3 - - command: .buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh cypress:run:qa:serverless:investigations - label: 'Serverless MKI QA Investigations - Security Solution Cypress Tests' + - command: .buildkite/scripts/pipelines/security_solution_quality_gate/upload_image_metadata.sh + label: "Upload runtime info" + key: upload_runtime_info + depends_on: build_image agents: queue: n2-4-spot - # TODO : Revise the timeout when the pipeline will be officially integrated with the quality gate. timeout_in_minutes: 300 - parallelism: 8 retry: automatic: - - exit_status: '*' + - exit_status: "*" limit: 1 - - command: .buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh cypress:run:qa:serverless:rule_management - label: 'Serverless MKI QA Rule Management - Security Solution Cypress Tests' - agents: - queue: n2-4-spot - # TODO : Revise the timeout when the pipeline will be officially integrated with the quality gate. - timeout_in_minutes: 300 - parallelism: 8 - retry: - automatic: - - exit_status: '*' - limit: 1 + - group: "Execute Tests" + depends_on: build_image + steps: + # - command: .buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh cypress:run:qa:serverless:explore + # label: 'Serverless MKI QA Explore - Security Solution Cypress Tests' + # agents: + # queue: n2-4-spot + # # TODO : Revise the timeout when the pipeline will be officially integrated with the quality gate. + # timeout_in_minutes: 300 + # parallelism: 4 + # retry: + # automatic: + # - exit_status: '*' + # limit: 1 - - command: .buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh cypress:run:qa:serverless:rule_management:prebuilt_rules - label: 'Serverless MKI QA Rule Management - Prebuilt Rules - Security Solution Cypress Tests' - agents: - queue: n2-4-spot - # TODO : Revise the timeout when the pipeline will be officially integrated with the quality gate. - timeout_in_minutes: 300 - parallelism: 2 - retry: - automatic: - - exit_status: '*' - limit: 1 + # - command: .buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh cypress:run:qa:serverless:investigations + # label: 'Serverless MKI QA Investigations - Security Solution Cypress Tests' + # agents: + # queue: n2-4-spot + # # TODO : Revise the timeout when the pipeline will be officially integrated with the quality gate. + # timeout_in_minutes: 300 + # parallelism: 8 + # retry: + # automatic: + # - exit_status: '*' + # limit: 1 - - command: .buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh cypress:run:qa:serverless:detection_engine - label: 'Serverless MKI QA Detection Engine - Security Solution Cypress Tests' - agents: - queue: n2-4-spot - # TODO : Revise the timeout when the pipeline will be officially integrated with the quality gate. - timeout_in_minutes: 300 - parallelism: 8 - retry: - automatic: - - exit_status: '*' - limit: 1 + # - command: .buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh cypress:run:qa:serverless:rule_management + # label: 'Serverless MKI QA Rule Management - Security Solution Cypress Tests' + # agents: + # queue: n2-4-spot + # # TODO : Revise the timeout when the pipeline will be officially integrated with the quality gate. + # timeout_in_minutes: 300 + # parallelism: 8 + # retry: + # automatic: + # - exit_status: '*' + # limit: 1 - - command: .buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh cypress:run:qa:serverless:detection_engine:exceptions - label: 'Serverless MKI QA Detection Engine - Exceptions - Security Solution Cypress Tests' - agents: - queue: n2-4-spot - # TODO : Revise the timeout when the pipeline will be officially integrated with the quality gate. - timeout_in_minutes: 300 - parallelism: 6 - retry: - automatic: - - exit_status: '*' - limit: 1 + # - command: .buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh cypress:run:qa:serverless:rule_management:prebuilt_rules + # label: 'Serverless MKI QA Rule Management - Prebuilt Rules - Security Solution Cypress Tests' + # agents: + # queue: n2-4-spot + # # TODO : Revise the timeout when the pipeline will be officially integrated with the quality gate. + # timeout_in_minutes: 300 + # parallelism: 2 + # retry: + # automatic: + # - exit_status: '*' + # limit: 1 - - command: .buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh cypress:run:qa:serverless:ai_assistant - label: 'Serverless MKI QA AI Assistant - Security Solution Cypress Tests' - agents: - queue: n2-4-spot - # TODO : Revise the timeout when the pipeline will be officially integrated with the quality gate. - timeout_in_minutes: 300 - parallelism: 1 - retry: - automatic: - - exit_status: '*' - limit: 1 + # - command: .buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh cypress:run:qa:serverless:detection_engine + # label: 'Serverless MKI QA Detection Engine - Security Solution Cypress Tests' + # agents: + # queue: n2-4-spot + # # TODO : Revise the timeout when the pipeline will be officially integrated with the quality gate. + # timeout_in_minutes: 300 + # parallelism: 8 + # retry: + # automatic: + # - exit_status: '*' + # limit: 1 - - command: .buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh cypress:run:qa:serverless:entity_analytics - label: 'Serverless MKI QA Entity Analytics - Security Solution Cypress Tests' - agents: - queue: n2-4-spot - # TODO : Revise the timeout when the pipeline will be officially integrated with the quality gate. - timeout_in_minutes: 300 - parallelism: 2 - retry: - automatic: - - exit_status: '*' - limit: 1 \ No newline at end of file + # - command: .buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh cypress:run:qa:serverless:detection_engine:exceptions + # label: 'Serverless MKI QA Detection Engine - Exceptions - Security Solution Cypress Tests' + # agents: + # queue: n2-4-spot + # # TODO : Revise the timeout when the pipeline will be officially integrated with the quality gate. + # timeout_in_minutes: 300 + # parallelism: 6 + # retry: + # automatic: + # - exit_status: '*' + # limit: 1 + + - command: .buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh cypress:run:qa:serverless:ai_assistant + label: 'Serverless MKI QA AI Assistant - Security Solution Cypress Tests' + agents: + queue: n2-4-spot + # TODO : Revise the timeout when the pipeline will be officially integrated with the quality gate. + timeout_in_minutes: 300 + parallelism: 1 + retry: + automatic: + - exit_status: '*' + limit: 1 + + # - command: .buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh cypress:run:qa:serverless:entity_analytics + # label: 'Serverless MKI QA Entity Analytics - Security Solution Cypress Tests' + # agents: + # queue: n2-4-spot + # # TODO : Revise the timeout when the pipeline will be officially integrated with the quality gate. + # timeout_in_minutes: 300 + # parallelism: 2 + # retry: + # automatic: + # - exit_status: '*' + # limit: 1 \ No newline at end of file diff --git a/.buildkite/scripts/common/activate_service_account.sh b/.buildkite/scripts/common/activate_service_account.sh new file mode 100755 index 0000000000000..e5cd116a7bce1 --- /dev/null +++ b/.buildkite/scripts/common/activate_service_account.sh @@ -0,0 +1,85 @@ +#!/usr/bin/env bash + +set -euo pipefail + +source "$(dirname "${BASH_SOURCE[0]}")/vault_fns.sh" + +BUCKET_OR_EMAIL="${1:-}" +GCLOUD_EMAIL_POSTFIX="elastic-kibana-ci.iam.gserviceaccount.com" +GCLOUD_SA_PROXY_EMAIL="kibana-ci-sa-proxy@$GCLOUD_EMAIL_POSTFIX" + +if [[ -z "$BUCKET_OR_EMAIL" ]]; then + echo "Usage: $0 " + exit 1 +elif [[ "$BUCKET_OR_EMAIL" == "--unset-impersonation" ]]; then + echo "Unsetting impersonation" + gcloud config unset auth/impersonate_service_account + exit 0 +elif [[ "$BUCKET_OR_EMAIL" == "--logout-gcloud" ]]; then + echo "Logging out of gcloud" + if [[ -x "$(command -v gcloud)" ]] && [[ "$(gcloud auth list 2>/dev/null | grep $GCLOUD_SA_PROXY_EMAIL)" != "" ]]; then + gcloud auth revoke $GCLOUD_SA_PROXY_EMAIL --no-user-output-enabled + fi + exit 0 +fi + +CURRENT_GCLOUD_USER=$(gcloud auth list --filter="status=ACTIVE" --format="value(account)") + +# Verify that the service account proxy is activated +if [[ "$CURRENT_GCLOUD_USER" != "$GCLOUD_SA_PROXY_EMAIL" ]]; then + if [[ -x "$(command -v gcloud)" ]]; then + if [[ -z "${KIBANA_SERVICE_ACCOUNT_PROXY_KEY:-}" ]]; then + echo "KIBANA_SERVICE_ACCOUNT_PROXY_KEY is not set, cannot activate service account $GCLOUD_SA_PROXY_EMAIL." + exit 1 + fi + + AUTH_RESULT=$(gcloud auth activate-service-account --key-file="$KIBANA_SERVICE_ACCOUNT_PROXY_KEY" || "FAILURE") + if [[ "$AUTH_RESULT" == "FAILURE" ]]; then + echo "Failed to activate service account $GCLOUD_SA_PROXY_EMAIL." + exit 1 + else + echo "Activated service account $GCLOUD_SA_PROXY_EMAIL" + fi + else + echo "gcloud is not installed, cannot activate service account $GCLOUD_SA_PROXY_EMAIL." + exit 1 + fi +fi + +# Check if the arg is a service account e-mail or a bucket name +EMAIL="" +if [[ "$BUCKET_OR_EMAIL" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then + EMAIL="$BUCKET_OR_EMAIL" +elif [[ "$BUCKET_OR_EMAIL" =~ ^gs://* ]]; then + BUCKET_NAME="${BUCKET_OR_EMAIL:5}" +else + BUCKET_NAME="$BUCKET_OR_EMAIL" +fi + +if [[ -z "$EMAIL" ]]; then + case "$BUCKET_NAME" in + "elastic-kibana-coverage-live") + EMAIL="kibana-ci-access-coverage@$GCLOUD_EMAIL_POSTFIX" + ;; + "kibana-ci-es-snapshots-daily") + EMAIL="kibana-ci-access-es-snapshots@$GCLOUD_EMAIL_POSTFIX" + ;; + "kibana-so-types-snapshots") + EMAIL="kibana-ci-access-so-snapshots@$GCLOUD_EMAIL_POSTFIX" + ;; + "kibana-performance") + EMAIL="kibana-ci-access-perf-stats@$GCLOUD_EMAIL_POSTFIX" + ;; + "ci-artifacts.kibana.dev") + EMAIL="kibana-ci-access-artifacts@$GCLOUD_EMAIL_POSTFIX" + ;; + *) + EMAIL="$BUCKET_NAME@$GCLOUD_EMAIL_POSTFIX" + ;; + esac +fi + +# Activate the service account +echo "Impersonating $EMAIL" +gcloud config set auth/impersonate_service_account "$EMAIL" +echo "Activated service account $EMAIL" diff --git a/.buildkite/scripts/common/setup_bazel.sh b/.buildkite/scripts/common/setup_bazel.sh index ea3c2453de6d2..5461f713e0af3 100755 --- a/.buildkite/scripts/common/setup_bazel.sh +++ b/.buildkite/scripts/common/setup_bazel.sh @@ -2,6 +2,8 @@ source .buildkite/scripts/common/util.sh +echo '--- Setting up bazel' + echo "[bazel] writing .bazelrc" cat < $KIBANA_DIR/.bazelrc # Generated by .buildkite/scripts/common/setup_bazel.sh @@ -27,16 +29,16 @@ if [[ "$BAZEL_CACHE_MODE" == "gcs" ]]; then echo "[bazel] using GCS bucket: $BAZEL_BUCKET" -cat <> $KIBANA_DIR/.bazelrc + cat <> $KIBANA_DIR/.bazelrc build --remote_cache=https://storage.googleapis.com/$BAZEL_BUCKET - build --google_default_credentials + build --google_credentials=$BAZEL_REMOTE_CACHE_CREDENTIALS_FILE EOF fi if [[ "$BAZEL_CACHE_MODE" == "populate-local-gcs" ]]; then echo "[bazel] enabling caching with GCS buckets for local dev" -cat <> $KIBANA_DIR/.bazelrc + cat <> $KIBANA_DIR/.bazelrc build --remote_cache=https://storage.googleapis.com/kibana-local-bazel-remote-cache build --google_credentials=$BAZEL_LOCAL_DEV_CACHE_CREDENTIALS_FILE EOF diff --git a/.buildkite/scripts/common/util.sh b/.buildkite/scripts/common/util.sh index f80c89678c221..818d712fd2aa8 100755 --- a/.buildkite/scripts/common/util.sh +++ b/.buildkite/scripts/common/util.sh @@ -1,5 +1,7 @@ #!/usr/bin/env bash +source "$(dirname "${BASH_SOURCE[0]}")/vault_fns.sh" + is_pr() { [[ "${GITHUB_PR_NUMBER-}" ]] && return false @@ -170,48 +172,3 @@ npm_install_global() { download_artifact() { retry 3 1 timeout 3m buildkite-agent artifact download "$@" } - -# TODO: remove after https://github.com/elastic/kibana-operations/issues/15 is done -if [[ "${VAULT_ADDR:-}" == *"secrets.elastic.co"* ]]; then - VAULT_PATH_PREFIX="secret/kibana-issues/dev" - VAULT_KV_PREFIX="secret/kibana-issues/dev" - IS_LEGACY_VAULT_ADDR=true -else - VAULT_PATH_PREFIX="secret/ci/elastic-kibana" - VAULT_KV_PREFIX="kv/ci-shared/kibana-deployments" - IS_LEGACY_VAULT_ADDR=false -fi -export IS_LEGACY_VAULT_ADDR - -vault_get() { - key_path=$1 - field=$2 - - fullPath="$VAULT_PATH_PREFIX/$key_path" - - if [[ -z "${2:-}" || "${2:-}" =~ ^-.* ]]; then - retry 5 5 vault read "$fullPath" "${@:2}" - else - retry 5 5 vault read -field="$field" "$fullPath" "${@:3}" - fi -} - -vault_set() { - key_path=$1 - shift - fields=("$@") - - - fullPath="$VAULT_PATH_PREFIX/$key_path" - - # shellcheck disable=SC2068 - retry 5 5 vault write "$fullPath" ${fields[@]} -} - -vault_kv_set() { - kv_path=$1 - shift - fields=("$@") - - vault kv put "$VAULT_KV_PREFIX/$kv_path" "${fields[@]}" -} diff --git a/.buildkite/scripts/common/vault_fns.sh b/.buildkite/scripts/common/vault_fns.sh new file mode 100644 index 0000000000000..a7b92a4b05d6d --- /dev/null +++ b/.buildkite/scripts/common/vault_fns.sh @@ -0,0 +1,67 @@ +#!/bin/bash + +# TODO: remove after https://github.com/elastic/kibana-operations/issues/15 is done +if [[ "${VAULT_ADDR:-}" == *"secrets.elastic.co"* ]]; then + VAULT_PATH_PREFIX="secret/kibana-issues/dev" + VAULT_KV_PREFIX="secret/kibana-issues/dev" + IS_LEGACY_VAULT_ADDR=true +else + VAULT_PATH_PREFIX="secret/ci/elastic-kibana" + VAULT_KV_PREFIX="kv/ci-shared/kibana-deployments" + IS_LEGACY_VAULT_ADDR=false +fi +export IS_LEGACY_VAULT_ADDR + +retry() { + local retries=$1; shift + local delay=$1; shift + local attempts=1 + + until "$@"; do + retry_exit_status=$? + echo "Exited with $retry_exit_status" >&2 + if (( retries == "0" )); then + return $retry_exit_status + elif (( attempts == retries )); then + echo "Failed $attempts retries" >&2 + return $retry_exit_status + else + echo "Retrying $((retries - attempts)) more times..." >&2 + attempts=$((attempts + 1)) + sleep "$delay" + fi + done +} + +vault_get() { + key_path=${1:-} + field=${2:-} + + fullPath="$VAULT_PATH_PREFIX/$key_path" + + if [[ -z "$field" || "$field" =~ ^-.* ]]; then + retry 5 5 vault read "$fullPath" "${@:2}" + else + retry 5 5 vault read -field="$field" "$fullPath" "${@:3}" + fi +} + +vault_set() { + key_path=$1 + shift + fields=("$@") + + + fullPath="$VAULT_PATH_PREFIX/$key_path" + + # shellcheck disable=SC2068 + retry 5 5 vault write "$fullPath" ${fields[@]} +} + +vault_kv_set() { + kv_path=$1 + shift + fields=("$@") + + vault kv put "$VAULT_KV_PREFIX/$kv_path" "${fields[@]}" +} diff --git a/.buildkite/scripts/lifecycle/post_command.sh b/.buildkite/scripts/lifecycle/post_command.sh index 620041151e583..479608ca33259 100755 --- a/.buildkite/scripts/lifecycle/post_command.sh +++ b/.buildkite/scripts/lifecycle/post_command.sh @@ -2,6 +2,10 @@ set -euo pipefail +echo '--- Log out of gcloud' +./.buildkite/scripts/common/activate_service_account.sh --unset-impersonation || echo "Failed to unset impersonation" +./.buildkite/scripts/common/activate_service_account.sh --logout-gcloud || echo "Failed to log out of gcloud" + echo '--- Agent Debug Info' ts-node .buildkite/scripts/lifecycle/print_agent_links.ts || true diff --git a/.buildkite/scripts/lifecycle/pre_command.sh b/.buildkite/scripts/lifecycle/pre_command.sh index c14cb2c578a8a..966ba22c1272c 100755 --- a/.buildkite/scripts/lifecycle/pre_command.sh +++ b/.buildkite/scripts/lifecycle/pre_command.sh @@ -167,6 +167,16 @@ BAZEL_LOCAL_DEV_CACHE_CREDENTIALS_FILE="$HOME/.kibana-ci-bazel-remote-cache-loca export BAZEL_LOCAL_DEV_CACHE_CREDENTIALS_FILE vault_get kibana-ci-bazel-remote-cache-local-dev service_account_json > "$BAZEL_LOCAL_DEV_CACHE_CREDENTIALS_FILE" +# Export key for accessing bazel remote cache's GCS bucket +BAZEL_REMOTE_CACHE_CREDENTIALS_FILE="$HOME/.kibana-ci-bazel-remote-cache-gcs.json" +export BAZEL_REMOTE_CACHE_CREDENTIALS_FILE +vault_get kibana-ci-bazel-remote-cache-sa-key key | base64 -d > "$BAZEL_REMOTE_CACHE_CREDENTIALS_FILE" + +# Setup GCS Service Account Proxy for CI +KIBANA_SERVICE_ACCOUNT_PROXY_KEY="$(mktemp -d)/kibana-gcloud-service-account.json" +export KIBANA_SERVICE_ACCOUNT_PROXY_KEY +vault_get kibana-ci-sa-proxy-key key | base64 -d > "$KIBANA_SERVICE_ACCOUNT_PROXY_KEY" + PIPELINE_PRE_COMMAND=${PIPELINE_PRE_COMMAND:-".buildkite/scripts/lifecycle/pipelines/$BUILDKITE_PIPELINE_SLUG/pre_command.sh"} if [[ -f "$PIPELINE_PRE_COMMAND" ]]; then source "$PIPELINE_PRE_COMMAND" diff --git a/.buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh b/.buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh similarity index 67% rename from .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh rename to .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh index 1e809b6da6fc4..096709dc5da43 100755 --- a/.buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh +++ b/.buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh @@ -15,15 +15,33 @@ cd x-pack/test/security_solution_api_integration set +e QA_API_KEY=$(vault_get security-solution-quality-gate qa_api_key) +QA_CONSOLE_URL=$(vault_get security-solution-quality-gate qa_console_url) # Generate a random 5-digit number random_number=$((10000 + $RANDOM % 90000)) -ENVIRONMENT_DETAILS=$(curl --location 'https://global.qa.cld.elstc.co/api/v1/serverless/projects/security' \ +if [ -z "${KIBANA_MKI_USE_LATEST_COMMIT+x}" ] || [ "$KIBANA_MKI_USE_LATEST_COMMIT" = "0" ]; then + ENVIRONMENT_DETAILS=$(curl --location "$QA_CONSOLE_URL/api/v1/serverless/projects/security" \ --header "Authorization: ApiKey $QA_API_KEY" \ --header 'Content-Type: application/json' \ --data '{ - "name": "ftr-integration-tests-'$random_number'", - "region_id": "aws-eu-west-1"}' | jq '.') + "name": "ftr-integration-tests-'$random_number'", + "region_id": "aws-eu-west-1"}' | jq '.') +else + KBN_COMMIT_HASH=${BUILDKITE_COMMIT:0:12} + ENVIRONMENT_DETAILS=$(curl --location "$QA_CONSOLE_URL/api/v1/serverless/projects/security" \ + --header "Authorization: ApiKey $QA_API_KEY" \ + --header 'Content-Type: application/json' \ + --data '{ + "name": "ftr-integration-tests-'$random_number'", + "region_id": "aws-eu-west-1", + "overrides": { + "kibana": { + "docker_image" : "docker.elastic.co/kibana-ci/kibana-serverless:sec-sol-qg-'$KBN_COMMIT_HASH'" + } + } + }' | jq '.') +fi + NAME=$(echo $ENVIRONMENT_DETAILS | jq -r '.name') ID=$(echo $ENVIRONMENT_DETAILS | jq -r '.id') ES_URL=$(echo $ENVIRONMENT_DETAILS | jq -r '.endpoints.elasticsearch') @@ -33,7 +51,7 @@ KB_URL=$(echo $ENVIRONMENT_DETAILS | jq -r '.endpoints.kibana') sleep 5 # Resetting the credentials of the elastic user in the project -CREDS_BODY=$(curl -s --location --request POST "https://global.qa.cld.elstc.co/api/v1/serverless/projects/security/$ID/_reset-credentials" \ +CREDS_BODY=$(curl -s --location --request POST "$QA_CONSOLE_URL/api/v1/serverless/projects/security/$ID/_reset-credentials" \ --header "Authorization: ApiKey $QA_API_KEY" \ --header 'Content-Type: application/json' | jq '.') USERNAME=$(echo $CREDS_BODY | jq -r '.username') @@ -76,7 +94,7 @@ TEST_CLOUD=1 TEST_ES_URL="https://elastic:$PASSWORD@$FORMATTED_ES_URL:443" TEST_ cmd_status=$? echo "Exit code with status: $cmd_status" -curl --location --request DELETE "https://global.qa.cld.elstc.co/api/v1/serverless/projects/security/$ID" \ +curl --location --request DELETE "$QA_CONSOLE_URL/api/v1/serverless/projects/security/$ID" \ --header "Authorization: ApiKey $QA_API_KEY" exit $cmd_status diff --git a/.buildkite/scripts/pipelines/security_solution_quality_gate/create_periodic_test_docker_image.sh b/.buildkite/scripts/pipelines/security_solution_quality_gate/create_periodic_test_docker_image.sh new file mode 100644 index 0000000000000..4e459e23ce25b --- /dev/null +++ b/.buildkite/scripts/pipelines/security_solution_quality_gate/create_periodic_test_docker_image.sh @@ -0,0 +1,83 @@ +#!/bin/bash +if [ -z "${KIBANA_MKI_USE_LATEST_COMMIT+x}" ] || [ "$KIBANA_MKI_USE_LATEST_COMMIT" = "0" ]; then + echo "As we not testing against latest kibana image, this step is exiting with exit code 0" + exit 0 +fi + + +.buildkite/scripts/bootstrap.sh + +source .buildkite/scripts/steps/artifacts/env.sh + +GIT_ABBREV_COMMIT=${BUILDKITE_COMMIT:0:12} +KIBANA_IMAGE_TAG="sec-sol-qg-$GIT_ABBREV_COMMIT" + + +KIBANA_BASE_IMAGE="docker.elastic.co/kibana-ci/kibana-serverless" +export KIBANA_IMAGE="$KIBANA_BASE_IMAGE:$KIBANA_IMAGE_TAG" + +echo "--- Verify manifest does not already exist" +echo "$KIBANA_DOCKER_PASSWORD" | docker login -u "$KIBANA_DOCKER_USERNAME" --password-stdin docker.elastic.co +trap 'docker logout docker.elastic.co' EXIT + +echo "Checking manifest for $KIBANA_IMAGE" +if docker manifest inspect $KIBANA_IMAGE &> /dev/null; then + echo "Manifest already exists, exiting" + exit 0 +fi + +docker pull $KIBANA_BASE_IMAGE:latest + +echo "--- Build images" +node scripts/build \ + --debug \ + --release \ + --docker-cross-compile \ + --docker-images \ + --docker-namespace="kibana-ci" \ + --docker-tag="$KIBANA_IMAGE_TAG" \ + --skip-docker-ubuntu \ + --skip-docker-ubi \ + --skip-docker-cloud \ + --skip-docker-contexts \ + --skip-cdn-assets + +echo "--- Tag images" +docker rmi "$KIBANA_IMAGE" +docker load < "target/kibana-serverless-$BASE_VERSION-docker-image.tar.gz" +docker tag "$KIBANA_IMAGE" "$KIBANA_IMAGE-amd64" + +docker rmi "$KIBANA_IMAGE" +docker load < "target/kibana-serverless-$BASE_VERSION-docker-image-aarch64.tar.gz" +docker tag "$KIBANA_IMAGE" "$KIBANA_IMAGE-arm64" + +echo "--- Push images" +docker image push "$KIBANA_IMAGE-arm64" +docker image push "$KIBANA_IMAGE-amd64" + +echo "--- Create and push manifests" +docker manifest create \ + "$KIBANA_IMAGE" \ + --amend "$KIBANA_IMAGE-arm64" \ + --amend "$KIBANA_IMAGE-amd64" +docker manifest push "$KIBANA_IMAGE" + +if [[ "$BUILDKITE_BRANCH" == "$KIBANA_BASE_BRANCH" ]] && [[ "${BUILDKITE_PULL_REQUEST:-false}" == "false" ]]; then + docker manifest create \ + "$KIBANA_BASE_IMAGE:latest" \ + --amend "$KIBANA_IMAGE-arm64" \ + --amend "$KIBANA_IMAGE-amd64" + docker manifest push "$KIBANA_BASE_IMAGE:latest" +fi + +docker logout docker.elastic.co + +cat << EOF | buildkite-agent annotate --style "info" --context image + ### Serverless Images + + Manifest: \`$KIBANA_IMAGE\` + + AMD64: \`$KIBANA_IMAGE-amd64\` + + ARM64: \`$KIBANA_IMAGE-arm64\` +EOF diff --git a/.buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh b/.buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh index e4b1701f161eb..8cce28a1401df 100755 --- a/.buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh +++ b/.buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh @@ -22,7 +22,14 @@ vault_get security-quality-gate/role-users data -format=json > .ftr/role_users.j cd x-pack/test/security_solution_cypress set +e +if [ -z "${KIBANA_MKI_USE_LATEST_COMMIT+x}" ] || [ "$KIBANA_MKI_USE_LATEST_COMMIT" = "0" ]; then + KIBANA_OVERRIDE_FLAG=0 +else + KIBANA_OVERRIDE_FLAG=1 +fi + QA_API_KEY=$(vault_get security-solution-quality-gate qa_api_key) +QA_CONSOLE_URL=$(vault_get security-solution-quality-gate qa_console_url) BK_ANALYTICS_API_KEY=$(vault_get security-solution-quality-gate serverless-sec-sol-cypress-bk-api-key) -BK_ANALYTICS_API_KEY=$BK_ANALYTICS_API_KEY CLOUD_QA_API_KEY=$QA_API_KEY yarn $1; status=$?; yarn junit:merge || :; exit $status \ No newline at end of file +QA_CONSOLE_URL=$QA_CONSOLE_URL KIBANA_MKI_USE_LATEST_COMMIT=$KIBANA_OVERRIDE_FLAG BK_ANALYTICS_API_KEY=$BK_ANALYTICS_API_KEY CLOUD_QA_API_KEY=$QA_API_KEY yarn $1; status=$?; yarn junit:merge || :; exit $status diff --git a/.buildkite/scripts/pipelines/security_solution_quality_gate/upload_image_metadata.sh b/.buildkite/scripts/pipelines/security_solution_quality_gate/upload_image_metadata.sh new file mode 100644 index 0000000000000..c1a22d221cafc --- /dev/null +++ b/.buildkite/scripts/pipelines/security_solution_quality_gate/upload_image_metadata.sh @@ -0,0 +1,27 @@ +#!/bin/bash +echo "$KIBANA_DOCKER_PASSWORD" | docker login -u "$KIBANA_DOCKER_USERNAME" --password-stdin docker.elastic.co + +KIBANA_BASE_IMAGE="docker.elastic.co/kibana-ci/kibana-serverless" +KIBANA_CURRENT_COMMIT=${KIBANA_BASE_IMAGE}:sec-sol-qg-${BUILDKITE_COMMIT:0:12} +KIBANA_LATEST=${KIBANA_BASE_IMAGE}:latest + +if [ "$KIBANA_MKI_USE_LATEST_COMMIT" = "1" ]; then + KBN_IMAGE=${KIBANA_CURRENT_COMMIT} +else + KBN_IMAGE=${KIBANA_LATEST} +fi + +docker pull ${KBN_IMAGE} +build_date=$(docker inspect ${KBN_IMAGE} | jq -r '.[0].Config.Labels."org.label-schema.build-date"') +vcs_ref=$(docker inspect ${KBN_IMAGE} | jq -r '.[0].Config.Labels."org.label-schema.vcs-ref"') +vcs_url=$(docker inspect ${KBN_IMAGE} | jq -r '.[0].Config.Labels."org.label-schema.vcs-url"') +version=$(docker inspect ${KBN_IMAGE} | jq -r '.[0].Config.Labels."org.label-schema.version"') + +markdown_text=""" + # Kibana Container Metadata + - Build Date : $build_date + - Github Commit Hash : $vcs_ref + - Github Repo : $vcs_url + - Version : $version +""" +echo "${markdown_text//[*\\_]/\\&}" | buildkite-agent annotate --style "info" \ No newline at end of file diff --git a/.buildkite/scripts/steps/archive_so_migration_snapshot.sh b/.buildkite/scripts/steps/archive_so_migration_snapshot.sh index 3db3da975b41b..1c791608709c7 100755 --- a/.buildkite/scripts/steps/archive_so_migration_snapshot.sh +++ b/.buildkite/scripts/steps/archive_so_migration_snapshot.sh @@ -3,15 +3,16 @@ set -euo pipefail .buildkite/scripts/bootstrap.sh -SO_MIGRATIONS_SNAPSHOT_FOLDER=kibana-so-types-snapshots +SO_MIGRATIONS_SNAPSHOT_BUCKET="gs://kibana-so-types-snapshots" SNAPSHOT_FILE_PATH="${1:-target/plugin_so_types_snapshot.json}" echo "--- Creating snapshot of Saved Object migration info" node scripts/snapshot_plugin_types snapshot --outputPath "$SNAPSHOT_FILE_PATH" echo "--- Uploading as ${BUILDKITE_COMMIT}.json" -SNAPSHOT_PATH="${SO_MIGRATIONS_SNAPSHOT_FOLDER}/${BUILDKITE_COMMIT}.json" -gsutil cp "$SNAPSHOT_FILE_PATH" "gs://$SNAPSHOT_PATH" +SNAPSHOT_PATH="${SO_MIGRATIONS_SNAPSHOT_BUCKET}/${BUILDKITE_COMMIT}.json" +.buildkite/scripts/common/activate_service_account.sh "$SO_MIGRATIONS_SNAPSHOT_BUCKET" +gsutil cp "$SNAPSHOT_FILE_PATH" "$SNAPSHOT_PATH" buildkite-agent annotate --context so_migration_snapshot --style success \ 'Saved Object type snapshot is available at '"$SNAPSHOT_PATH"'' diff --git a/.buildkite/scripts/steps/code_coverage/reporting/downloadPrevSha.sh b/.buildkite/scripts/steps/code_coverage/reporting/downloadPrevSha.sh index af7824841ef28..a77cfbef54d55 100755 --- a/.buildkite/scripts/steps/code_coverage/reporting/downloadPrevSha.sh +++ b/.buildkite/scripts/steps/code_coverage/reporting/downloadPrevSha.sh @@ -6,6 +6,7 @@ set -euo pipefail gsutil -m cp -r gs://elastic-bekitzur-kibana-coverage-live/previous_pointer/previous.txt . || echo "### Previous Pointer NOT FOUND?" # TODO: Activate after the above is removed +#.buildkite/scripts/common/activate_service_account.sh gs://elastic-kibana-coverage-live #gsutil -m cp -r gs://elastic-kibana-coverage-live/previous_pointer/previous.txt . || echo "### Previous Pointer NOT FOUND?" if [ -e ./previous.txt ]; then diff --git a/.buildkite/scripts/steps/code_coverage/reporting/uploadPrevSha.sh b/.buildkite/scripts/steps/code_coverage/reporting/uploadPrevSha.sh index 26d84fa7d6024..42ef5faa5cd3d 100755 --- a/.buildkite/scripts/steps/code_coverage/reporting/uploadPrevSha.sh +++ b/.buildkite/scripts/steps/code_coverage/reporting/uploadPrevSha.sh @@ -12,4 +12,5 @@ collectPrevious # TODO: Safe to remove this after 2024-03-01 (https://github.com/elastic/kibana/issues/175904) gsutil cp previous.txt gs://elastic-bekitzur-kibana-coverage-live/previous_pointer/ +.buildkite/scripts/common/activate_service_account.sh gs://elastic-kibana-coverage-live gsutil cp previous.txt gs://elastic-kibana-coverage-live/previous_pointer/ diff --git a/.buildkite/scripts/steps/code_coverage/reporting/uploadStaticSite.sh b/.buildkite/scripts/steps/code_coverage/reporting/uploadStaticSite.sh index 20e85a51c3776..5bd0c07cc9b9b 100755 --- a/.buildkite/scripts/steps/code_coverage/reporting/uploadStaticSite.sh +++ b/.buildkite/scripts/steps/code_coverage/reporting/uploadStaticSite.sh @@ -27,5 +27,6 @@ uploadRest() { echo "--- Uploading static site" +.buildkite/scripts/common/activate_service_account.sh gs://elastic-kibana-coverage-live uploadBase uploadRest diff --git a/.buildkite/scripts/steps/es_serverless/promote_es_serverless_image.sh b/.buildkite/scripts/steps/es_serverless/promote_es_serverless_image.sh index d99cff34f6fbb..6a59959ea6fed 100755 --- a/.buildkite/scripts/steps/es_serverless/promote_es_serverless_image.sh +++ b/.buildkite/scripts/steps/es_serverless/promote_es_serverless_image.sh @@ -7,9 +7,6 @@ source .buildkite/scripts/common/util.sh BASE_ES_SERVERLESS_REPO=docker.elastic.co/elasticsearch-ci/elasticsearch-serverless TARGET_IMAGE=docker.elastic.co/kibana-ci/elasticsearch-serverless:latest-verified -ES_SERVERLESS_BUCKET=kibana-ci-es-serverless-images -MANIFEST_FILE_NAME=latest-verified.json - SOURCE_IMAGE_OR_TAG=$1 if [[ $SOURCE_IMAGE_OR_TAG =~ :[a-zA-Z_-]+$ ]]; then # $SOURCE_IMAGE_OR_TAG was a full image @@ -67,36 +64,6 @@ docker logout docker.elastic.co echo "Image push to $TARGET_IMAGE successful." echo "Promotion successful! Henceforth, thou shall be named Sir $TARGET_IMAGE" -MANIFEST_UPLOAD_PATH="Skipped" -if [[ "${PUBLISH_MANIFEST:-}" =~ ^(1|true)$ && "$SOURCE_IMAGE_OR_TAG" =~ ^git-[0-9a-fA-F]{12}$ ]]; then - echo "--- Uploading latest-verified manifest to GCS" - cat << EOT >> $MANIFEST_FILE_NAME -{ - "build_url": "$BUILDKITE_BUILD_URL", - "kibana_commit": "$BUILDKITE_COMMIT", - "kibana_branch": "$BUILDKITE_BRANCH", - "elasticsearch_serverless_tag": "$SOURCE_IMAGE_OR_TAG", - "elasticsearch_serverless_image_url": "$SOURCE_IMAGE", - "elasticsearch_serverless_commit": "TODO: this currently can't be decided", - "elasticsearch_commit": "$ELASTIC_COMMIT_HASH", - "created_at": "`date`", - "timestamp": "`FORCE_COLOR=0 node -p 'Date.now()'`" -} -EOT - - gsutil -h "Cache-Control:no-cache, max-age=0, no-transform" \ - cp $MANIFEST_FILE_NAME "gs://$ES_SERVERLESS_BUCKET/$MANIFEST_FILE_NAME" - gsutil acl ch -u AllUsers:R "gs://$ES_SERVERLESS_BUCKET/$MANIFEST_FILE_NAME" - MANIFEST_UPLOAD_PATH="$MANIFEST_FILE_NAME" - -elif [[ "${PUBLISH_MANIFEST:-}" =~ ^(1|true)$ ]]; then - echo "--- Skipping upload of latest-verified manifest to GCS, ES Serverless build tag is not pointing to a hash" -elif [[ "$SOURCE_IMAGE_OR_TAG" =~ ^git-[0-9a-fA-F]{12}$ ]]; then - echo "--- Skipping upload of latest-verified manifest to GCS, flag was not provided" -else - echo "--- Skipping upload of latest-verified manifest to GCS, no flag and hash provided" -fi - echo "--- Annotating build with info" cat << EOT | buildkite-agent annotate --style "success"

Promotion successful!

@@ -104,5 +71,4 @@ cat << EOT | buildkite-agent annotate --style "success"
Source image: $SOURCE_IMAGE
Kibana commit: $BUILDKITE_COMMIT
Elasticsearch commit: $ELASTIC_COMMIT_HASH -
Manifest file: $MANIFEST_UPLOAD_PATH EOT diff --git a/.buildkite/scripts/steps/es_snapshots/create_manifest.ts b/.buildkite/scripts/steps/es_snapshots/create_manifest.ts index 659a034d793bc..c2a3250c1e853 100644 --- a/.buildkite/scripts/steps/es_snapshots/create_manifest.ts +++ b/.buildkite/scripts/steps/es_snapshots/create_manifest.ts @@ -103,6 +103,7 @@ interface ManifestEntry { set -euo pipefail echo '--- Upload files to GCS' + .buildkite/scripts/common/activate_service_account.sh ${BASE_BUCKET_DAILY} cd "${destination}" gsutil -m cp -r *.* gs://${BASE_BUCKET_DAILY}/${DESTINATION} cp manifest.json manifest-latest.json diff --git a/.buildkite/scripts/steps/es_snapshots/promote_manifest.ts b/.buildkite/scripts/steps/es_snapshots/promote_manifest.ts index 7b0f9b7a319a8..3d4009139e3dc 100644 --- a/.buildkite/scripts/steps/es_snapshots/promote_manifest.ts +++ b/.buildkite/scripts/steps/es_snapshots/promote_manifest.ts @@ -38,6 +38,7 @@ import { BASE_BUCKET_DAILY, BASE_BUCKET_PERMANENT } from './bucket_config'; execSync( ` set -euo pipefail + .buildkite/scripts/common/activate_service_account.sh ${bucket} cp manifest.json manifest-latest-verified.json gsutil -h "Cache-Control:no-cache, max-age=0, no-transform" cp manifest-latest-verified.json gs://${BASE_BUCKET_DAILY}/${version}/ rm manifest.json diff --git a/.buildkite/scripts/steps/functional/scalability_dataset_extraction.sh b/.buildkite/scripts/steps/functional/scalability_dataset_extraction.sh index d22e9890b2de5..ebf9e28d2c9ea 100755 --- a/.buildkite/scripts/steps/functional/scalability_dataset_extraction.sh +++ b/.buildkite/scripts/steps/functional/scalability_dataset_extraction.sh @@ -41,6 +41,9 @@ download_artifact kibana-default-plugins.tar.gz "${OUTPUT_DIR}/" --build "${KIBA echo "--- Adding commit info" echo "${BUILDKITE_COMMIT}" > "${OUTPUT_DIR}/KIBANA_COMMIT_HASH" +echo "--- Activating service-account for gsutil to access gs://kibana-performance" +.buildkite/scripts/common/activate_service_account.sh gs://kibana-performance + echo "--- Uploading ${OUTPUT_REL} dir to ${GCS_BUCKET}" cd "${OUTPUT_DIR}/.." gsutil -m cp -r "${BUILD_ID}" "${GCS_BUCKET}" diff --git a/.buildkite/scripts/steps/scalability/benchmarking.sh b/.buildkite/scripts/steps/scalability/benchmarking.sh index 39acf4203af37..79875fb04d5fe 100755 --- a/.buildkite/scripts/steps/scalability/benchmarking.sh +++ b/.buildkite/scripts/steps/scalability/benchmarking.sh @@ -19,6 +19,9 @@ rm -rf "${KIBANA_LOAD_TESTING_DIR}" rm -rf "${GCS_ARTIFACTS_DIR}" download_artifacts() { + echo Activating service-account for gsutil to access gs://kibana-performance + .buildkite/scripts/common/activate_service_account.sh gs://kibana-performance + mkdir -p "${GCS_ARTIFACTS_DIR}" gsutil cp "$GCS_BUCKET/latest" "${GCS_ARTIFACTS_DIR}/" diff --git a/.buildkite/scripts/steps/storybooks/build_and_upload.ts b/.buildkite/scripts/steps/storybooks/build_and_upload.ts index 4ea59b4a8298d..e7545f5011df1 100644 --- a/.buildkite/scripts/steps/storybooks/build_and_upload.ts +++ b/.buildkite/scripts/steps/storybooks/build_and_upload.ts @@ -9,6 +9,7 @@ import { execSync } from 'child_process'; import fs from 'fs'; import path from 'path'; +import { getKibanaDir } from '#pipeline-utils'; // TODO - how to generate this dynamically? const STORYBOOKS = [ @@ -117,7 +118,12 @@ const upload = () => { fs.writeFileSync('index.html', html); console.log('--- Uploading Storybooks'); + const activateScript = path.relative( + process.cwd(), + path.join(getKibanaDir(), '.buildkite', 'scripts', 'common', 'activate_service_account.sh') + ); exec(` + ${activateScript} gs://ci-artifacts.kibana.dev gsutil -q -m cp -r -z js,css,html,json,map,txt,svg '*' 'gs://${STORYBOOK_BUCKET}/${STORYBOOK_DIRECTORY}/${process.env.BUILDKITE_COMMIT}/' gsutil -h "Cache-Control:no-cache, max-age=0, no-transform" cp -z html 'index.html' 'gs://${STORYBOOK_BUCKET}/${STORYBOOK_DIRECTORY}/latest/' `); diff --git a/.buildkite/scripts/steps/webpack_bundle_analyzer/upload.ts b/.buildkite/scripts/steps/webpack_bundle_analyzer/upload.ts index 73c24a82be6df..7bb45990bd467 100644 --- a/.buildkite/scripts/steps/webpack_bundle_analyzer/upload.ts +++ b/.buildkite/scripts/steps/webpack_bundle_analyzer/upload.ts @@ -9,6 +9,7 @@ import { execSync } from 'child_process'; import fs from 'fs'; import path from 'path'; +import { getKibanaDir } from '#pipeline-utils'; const GITHUB_CONTEXT = 'Build and Publish Webpack bundle analyzer reports'; @@ -54,7 +55,12 @@ const upload = () => { fs.writeFileSync('index.html', html); console.log('--- Uploading Webpack Bundle Analyzer reports'); + const activateScript = path.relative( + process.cwd(), + path.join(getKibanaDir(), '.buildkite', 'scripts', 'common', 'activate_service_account.sh') + ); exec(` + ${activateScript} gs://ci-artifacts.kibana.dev gsutil -q -m cp -r -z html '*' 'gs://${WEBPACK_REPORTS_BUCKET}/${WEBPACK_REPORTS}/${process.env.BUILDKITE_COMMIT}/' gsutil -h "Cache-Control:no-cache, max-age=0, no-transform" cp -z html 'index.html' 'gs://${WEBPACK_REPORTS_BUCKET}/${WEBPACK_REPORTS}/latest/' `); diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 02837458e3046..f45cb00fa1bbd 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -324,6 +324,7 @@ src/plugins/data_view_editor @elastic/kibana-data-discovery examples/data_view_field_editor_example @elastic/kibana-data-discovery src/plugins/data_view_field_editor @elastic/kibana-data-discovery src/plugins/data_view_management @elastic/kibana-data-discovery +packages/kbn-data-view-utils @elastic/kibana-data-discovery src/plugins/data_views @elastic/kibana-data-discovery x-pack/plugins/data_visualizer @elastic/ml-ui x-pack/plugins/dataset_quality @elastic/obs-ux-logs-team @@ -353,8 +354,8 @@ packages/kbn-docs-utils @elastic/kibana-operations packages/kbn-dom-drag-drop @elastic/kibana-visualizations @elastic/kibana-data-discovery packages/kbn-ebt-tools @elastic/kibana-core packages/kbn-ecs @elastic/kibana-core @elastic/security-threat-hunting-investigations -x-pack/packages/security-solution/ecs_data_quality_dashboard @elastic/security-threat-hunting-investigations -x-pack/plugins/ecs_data_quality_dashboard @elastic/security-threat-hunting-investigations +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 packages/kbn-elastic-agent-utils @elastic/obs-ux-logs-team x-pack/packages/kbn-elastic-assistant @elastic/security-generative-ai x-pack/packages/kbn-elastic-assistant-common @elastic/security-generative-ai diff --git a/api_docs/actions.mdx b/api_docs/actions.mdx index feb1f10df6397..54037cd3edeb5 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-02-05 +date: 2024-02-07 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 9d0c329071b23..fb9947f139e12 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'advancedSettings'] --- import advancedSettingsObj from './advanced_settings.devdocs.json'; diff --git a/api_docs/ai_assistant_management_observability.mdx b/api_docs/ai_assistant_management_observability.mdx index b0fd51fcc3ce3..32558d156f587 100644 --- a/api_docs/ai_assistant_management_observability.mdx +++ b/api_docs/ai_assistant_management_observability.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/aiAssistantManagementObservability title: "aiAssistantManagementObservability" image: https://source.unsplash.com/400x175/?github description: API docs for the aiAssistantManagementObservability plugin -date: 2024-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'aiAssistantManagementObservability'] --- import aiAssistantManagementObservabilityObj from './ai_assistant_management_observability.devdocs.json'; diff --git a/api_docs/ai_assistant_management_selection.mdx b/api_docs/ai_assistant_management_selection.mdx index 86dc5b4b3c34d..b83874aec8366 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-02-05 +date: 2024-02-07 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 6a4ad90613be7..fc291bd721831 100644 --- a/api_docs/aiops.devdocs.json +++ b/api_docs/aiops.devdocs.json @@ -1142,22 +1142,6 @@ "deprecated": false, "trackAdoption": false }, - { - "parentPluginId": "aiops", - "id": "def-public.LogRateAnalysisContentWrapperProps.setGlobalState", - "type": "Any", - "tags": [], - "label": "setGlobalState", - "description": [ - "On global timefilter update" - ], - "signature": [ - "any" - ], - "path": "x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_content_wrapper.tsx", - "deprecated": false, - "trackAdoption": false - }, { "parentPluginId": "aiops", "id": "def-public.LogRateAnalysisContentWrapperProps.initialAnalysisStart", diff --git a/api_docs/aiops.mdx b/api_docs/aiops.mdx index 1a9acc2f670b8..28e29c47bc22d 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'aiops'] --- import aiopsObj from './aiops.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) for questi | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 70 | 1 | 4 | 1 | +| 69 | 0 | 4 | 1 | ## Client diff --git a/api_docs/alerting.devdocs.json b/api_docs/alerting.devdocs.json index 033531cbd02be..2d02c951d2e79 100644 --- a/api_docs/alerting.devdocs.json +++ b/api_docs/alerting.devdocs.json @@ -3146,10 +3146,10 @@ }, { "parentPluginId": "alerting", - "id": "def-server.Rule.notificationDelay", + "id": "def-server.Rule.alertDelay", "type": "Object", "tags": [], - "label": "notificationDelay", + "label": "alertDelay", "description": [], "signature": [ "Readonly<{} & { active: number; }> | undefined" @@ -3257,7 +3257,7 @@ "section": "def-common.SanitizedRule", "text": "SanitizedRule" }, - ", \"id\" | \"consumer\" | \"name\" | \"actions\" | \"tags\" | \"enabled\" | \"schedule\" | \"createdBy\" | \"updatedBy\" | \"createdAt\" | \"updatedAt\" | \"throttle\" | \"muteAll\" | \"notifyWhen\" | \"snoozeSchedule\" | \"revision\"> & { producer: string; ruleTypeId: string; ruleTypeName: string; }" + ", \"id\" | \"consumer\" | \"name\" | \"actions\" | \"tags\" | \"enabled\" | \"schedule\" | \"createdBy\" | \"updatedBy\" | \"createdAt\" | \"updatedAt\" | \"throttle\" | \"muteAll\" | \"notifyWhen\" | \"snoozeSchedule\" | \"revision\" | \"alertDelay\"> & { producer: string; ruleTypeId: string; ruleTypeName: string; }" ], "path": "x-pack/plugins/alerting/server/types.ts", "deprecated": false, @@ -4071,6 +4071,28 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "alerting", + "id": "def-server.RuleType.schemas", + "type": "Object", + "tags": [], + "label": "schemas", + "description": [], + "signature": [ + "{ params?: { type: \"zod\"; schema: Zod.ZodObject | Zod.ZodIntersection; } | { type: \"config-schema\"; schema: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + "; } | undefined; } | undefined" + ], + "path": "x-pack/plugins/alerting/server/types.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "alerting", "id": "def-server.RuleType.actionGroups", @@ -6292,6 +6314,48 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "alerting", + "id": "def-common.AlertDelay", + "type": "Interface", + "tags": [], + "label": "AlertDelay", + "description": [], + "signature": [ + { + "pluginId": "alerting", + "scope": "common", + "docId": "kibAlertingPluginApi", + "section": "def-common.AlertDelay", + "text": "AlertDelay" + }, + " extends ", + { + "pluginId": "@kbn/core-saved-objects-common", + "scope": "common", + "docId": "kibKbnCoreSavedObjectsCommonPluginApi", + "section": "def-common.SavedObjectAttributes", + "text": "SavedObjectAttributes" + } + ], + "path": "x-pack/plugins/alerting/common/rule.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "alerting", + "id": "def-common.AlertDelay.active", + "type": "number", + "tags": [], + "label": "active", + "description": [], + "path": "x-pack/plugins/alerting/common/rule.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "alerting", "id": "def-common.AlertingFrameworkHealth", @@ -7823,31 +7887,6 @@ ], "initialIsOpen": false }, - { - "parentPluginId": "alerting", - "id": "def-common.NotificationDelay", - "type": "Interface", - "tags": [], - "label": "NotificationDelay", - "description": [], - "path": "x-pack/plugins/alerting/common/rule.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "alerting", - "id": "def-common.NotificationDelay.active", - "type": "number", - "tags": [], - "label": "active", - "description": [], - "path": "x-pack/plugins/alerting/common/rule.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, { "parentPluginId": "alerting", "id": "def-common.Rule", @@ -8345,18 +8384,18 @@ }, { "parentPluginId": "alerting", - "id": "def-common.Rule.notificationDelay", + "id": "def-common.Rule.alertDelay", "type": "Object", "tags": [], - "label": "notificationDelay", + "label": "alertDelay", "description": [], "signature": [ { "pluginId": "alerting", "scope": "common", "docId": "kibAlertingPluginApi", - "section": "def-common.NotificationDelay", - "text": "NotificationDelay" + "section": "def-common.AlertDelay", + "text": "AlertDelay" }, " | undefined" ], @@ -11524,7 +11563,7 @@ "section": "def-common.SanitizedRule", "text": "SanitizedRule" }, - ", \"id\" | \"consumer\" | \"name\" | \"actions\" | \"tags\" | \"enabled\" | \"schedule\" | \"createdBy\" | \"updatedBy\" | \"createdAt\" | \"updatedAt\" | \"throttle\" | \"muteAll\" | \"notifyWhen\" | \"snoozeSchedule\" | \"revision\"> & { producer: string; ruleTypeId: string; ruleTypeName: string; }" + ", \"id\" | \"consumer\" | \"name\" | \"actions\" | \"tags\" | \"enabled\" | \"schedule\" | \"createdBy\" | \"updatedBy\" | \"createdAt\" | \"updatedAt\" | \"throttle\" | \"muteAll\" | \"notifyWhen\" | \"snoozeSchedule\" | \"revision\" | \"alertDelay\"> & { producer: string; ruleTypeId: string; ruleTypeName: string; }" ], "path": "x-pack/plugins/alerting/common/rule.ts", "deprecated": false, @@ -11539,7 +11578,7 @@ "label": "TrackedLifecycleAlertState", "description": [], "signature": [ - "{ alertId: string; alertUuid: string; started: string; flappingHistory: boolean[]; flapping: boolean; pendingRecoveredCount: number; }" + "{ alertId: string; alertUuid: string; started: string; flappingHistory: boolean[]; flapping: boolean; pendingRecoveredCount: number; activeCount: number; }" ], "path": "x-pack/packages/kbn-alerting-state-types/src/lifecycle_state.ts", "deprecated": false, @@ -11579,7 +11618,7 @@ "\nThis is redefined instead of derived from above `wrappedStateRt` because\nthere's no easy way to instantiate generic values such as the runtime type\nfactory function." ], "signature": [ - "RuleTypeState & { wrapped: State; trackedAlerts: Record; trackedAlertsRecovered: Record; }" + "RuleTypeState & { wrapped: State; trackedAlerts: Record; trackedAlertsRecovered: Record; }" ], "path": "x-pack/packages/kbn-alerting-state-types/src/lifecycle_state.ts", "deprecated": false, diff --git a/api_docs/alerting.mdx b/api_docs/alerting.mdx index 726952a004730..5870a15f134ac 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'alerting'] --- import alertingObj from './alerting.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-o | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 834 | 1 | 803 | 51 | +| 835 | 1 | 804 | 51 | ## Client diff --git a/api_docs/apm.mdx b/api_docs/apm.mdx index f2d9bf9145ddf..47b13e31ab939 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'apm'] --- import apmObj from './apm.devdocs.json'; diff --git a/api_docs/apm_data_access.mdx b/api_docs/apm_data_access.mdx index 4d670ed96caa6..55a18a78b6ad7 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'apmDataAccess'] --- import apmDataAccessObj from './apm_data_access.devdocs.json'; diff --git a/api_docs/asset_manager.mdx b/api_docs/asset_manager.mdx index 703017e9387f8..31fff0681b7e8 100644 --- a/api_docs/asset_manager.mdx +++ b/api_docs/asset_manager.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/assetManager title: "assetManager" image: https://source.unsplash.com/400x175/?github description: API docs for the assetManager plugin -date: 2024-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'assetManager'] --- import assetManagerObj from './asset_manager.devdocs.json'; diff --git a/api_docs/banners.mdx b/api_docs/banners.mdx index 3b0181fc03d20..e4979db414c53 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-02-05 +date: 2024-02-07 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 28840c59af2d3..fc66ffd30c496 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-02-05 +date: 2024-02-07 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 3c136d3bd8fd0..76e21cb4a00f4 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-02-05 +date: 2024-02-07 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 003adab6cbc48..ce1a79df312ba 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-02-05 +date: 2024-02-07 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 ee856223b69eb..03529c94a9eaa 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-02-05 +date: 2024-02-07 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 c9781d96ebe70..f733add4b9991 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-02-05 +date: 2024-02-07 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 514bcbb6e036d..fcbf269d5375f 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-02-05 +date: 2024-02-07 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 08780844dc6ca..bb51d1645f85e 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudDefend'] --- import cloudDefendObj from './cloud_defend.devdocs.json'; diff --git a/api_docs/cloud_experiments.mdx b/api_docs/cloud_experiments.mdx index d3e1f0c19dfd1..e6d0c449ec1ad 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-02-05 +date: 2024-02-07 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 9d7a4f3aa6a38..080f0d5cb0c3a 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudSecurityPosture'] --- import cloudSecurityPostureObj from './cloud_security_posture.devdocs.json'; diff --git a/api_docs/console.mdx b/api_docs/console.mdx index 747fe019f7efa..27b4958284e5a 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'console'] --- import consoleObj from './console.devdocs.json'; diff --git a/api_docs/content_management.devdocs.json b/api_docs/content_management.devdocs.json index 7a800426b50ad..16a91ff0d2ac6 100644 --- a/api_docs/content_management.devdocs.json +++ b/api_docs/content_management.devdocs.json @@ -1283,7 +1283,7 @@ "section": "def-server.StorageContext", "text": "StorageContext" }, - ", id: string, options?: object | undefined) => Promise<{ item: T; } | { item: T; meta: any; }>" + ", id: string, options?: object | undefined) => Promise<{ item: T; meta?: undefined; } | { item: T; meta: any; }>" ], "path": "src/plugins/content_management/server/core/types.ts", "deprecated": false, @@ -1361,7 +1361,7 @@ "section": "def-server.StorageContext", "text": "StorageContext" }, - ", ids: string[], options?: object | undefined) => Promise<{ hits: ({ item: T; } | { item: T; meta: any; })[]; }>" + ", ids: string[], options?: object | undefined) => Promise<{ hits: ({ item: T; meta?: undefined; } | { item: T; meta: any; })[]; }>" ], "path": "src/plugins/content_management/server/core/types.ts", "deprecated": false, @@ -1439,7 +1439,7 @@ "section": "def-server.StorageContext", "text": "StorageContext" }, - ", data: object, options?: object | undefined) => Promise<{ item: T; } | { item: T; meta: any; }>" + ", data: object, options?: object | undefined) => Promise<{ item: T; meta?: undefined; } | { item: T; meta: any; }>" ], "path": "src/plugins/content_management/server/core/types.ts", "deprecated": false, @@ -1517,7 +1517,7 @@ "section": "def-server.StorageContext", "text": "StorageContext" }, - ", id: string, data: object, options?: object | undefined) => Promise<{ item: U; } | { item: U; meta: any; }>" + ", id: string, data: object, options?: object | undefined) => Promise<{ item: U; meta?: undefined; } | { item: U; meta: any; }>" ], "path": "src/plugins/content_management/server/core/types.ts", "deprecated": false, @@ -1941,7 +1941,9 @@ "type": "Object", "tags": [], "label": "requestHandlerContext", - "description": [], + "description": [ + "The Core HTTP request handler context" + ], "signature": [ { "pluginId": "@kbn/core-http-request-handler-context-server", @@ -2833,7 +2835,7 @@ "label": "CreateResult", "description": [], "signature": [ - "M extends void ? { item: T; } : { item: T; meta: M; }" + "M extends void ? { item: T; meta?: undefined; } : { item: T; meta: M; }" ], "path": "src/plugins/content_management/common/rpc/create.ts", "deprecated": false, @@ -2848,7 +2850,7 @@ "label": "GetResult", "description": [], "signature": [ - "M extends void ? { item: T; } : { item: T; meta: M; }" + "M extends void ? { item: T; meta?: undefined; } : { item: T; meta: M; }" ], "path": "src/plugins/content_management/common/rpc/get.ts", "deprecated": false, @@ -2944,7 +2946,7 @@ "label": "UpdateResult", "description": [], "signature": [ - "M extends void ? { item: T; } : { item: T; meta: M; }" + "M extends void ? { item: T; meta?: undefined; } : { item: T; meta: M; }" ], "path": "src/plugins/content_management/common/rpc/update.ts", "deprecated": false, diff --git a/api_docs/content_management.mdx b/api_docs/content_management.mdx index e2918f20e4918..dafa7bce29e1f 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'contentManagement'] --- import contentManagementObj from './content_management.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 | |-------------------|-----------|------------------------|-----------------| -| 149 | 0 | 126 | 6 | +| 149 | 0 | 125 | 6 | ## Client diff --git a/api_docs/controls.mdx b/api_docs/controls.mdx index bd579234400af..64d8ca3fd8e79 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-02-05 +date: 2024-02-07 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 6a5f2ce88ac1a..4454a70238943 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-02-05 +date: 2024-02-07 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 ee7f46eef48d4..e82c5a1de3616 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-02-05 +date: 2024-02-07 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 d24c174ad1f0d..4efbf0051269b 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-02-05 +date: 2024-02-07 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 58d3d9fbf9ceb..48315db6ab3eb 100644 --- a/api_docs/data.devdocs.json +++ b/api_docs/data.devdocs.json @@ -17339,6 +17339,20 @@ "path": "src/plugins/data_views/server/fetcher/index_patterns_fetcher.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-server.FieldDescriptor.defaultFormatter", + "type": "string", + "tags": [], + "label": "defaultFormatter", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/data_views/server/fetcher/index_patterns_fetcher.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -20030,7 +20044,7 @@ }, " | undefined; fixedInterval?: string[] | undefined; timeZone?: string[] | undefined; timeSeriesDimension?: boolean | undefined; timeSeriesMetric?: ", "MappingTimeSeriesMetricType", - " | undefined; shortDotsEnable?: boolean | undefined; isMapped?: boolean | undefined; parentName?: string | undefined; }" + " | undefined; shortDotsEnable?: boolean | undefined; isMapped?: boolean | undefined; parentName?: string | undefined; defaultFormatter?: string | undefined; }" ], "path": "src/plugins/data_views/common/fields/data_view_field.ts", "deprecated": false, @@ -20106,6 +20120,20 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "data", + "id": "def-common.DataViewField.defaultFormatter", + "type": "string", + "tags": [], + "label": "defaultFormatter", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/data_views/common/fields/data_view_field.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "data", "id": "def-common.DataViewField.runtimeField", @@ -20693,7 +20721,7 @@ "section": "def-common.IFieldSubType", "text": "IFieldSubType" }, - " | undefined; customLabel: string | undefined; }" + " | undefined; customLabel: string | undefined; defaultFormatter: string | undefined; }" ], "path": "src/plugins/data_views/common/fields/data_view_field.ts", "deprecated": false, @@ -25174,7 +25202,7 @@ }, " | undefined; fixedInterval?: string[] | undefined; timeZone?: string[] | undefined; timeSeriesDimension?: boolean | undefined; timeSeriesMetric?: ", "MappingTimeSeriesMetricType", - " | undefined; shortDotsEnable?: boolean | undefined; isMapped?: boolean | undefined; parentName?: string | undefined; }" + " | undefined; shortDotsEnable?: boolean | undefined; isMapped?: boolean | undefined; parentName?: string | undefined; defaultFormatter?: string | undefined; }" ], "path": "src/plugins/data_views/common/types.ts", "deprecated": false, diff --git a/api_docs/data.mdx b/api_docs/data.mdx index e8d0bcc0b1d1e..318245e38595a 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-02-05 +date: 2024-02-07 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 | |-------------------|-----------|------------------------|-----------------| -| 3235 | 31 | 2583 | 23 | +| 3237 | 31 | 2585 | 23 | ## Client diff --git a/api_docs/data_query.mdx b/api_docs/data_query.mdx index bfa8526571d50..05e3acb062a50 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-02-05 +date: 2024-02-07 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 | |-------------------|-----------|------------------------|-----------------| -| 3235 | 31 | 2583 | 23 | +| 3237 | 31 | 2585 | 23 | ## Client diff --git a/api_docs/data_search.mdx b/api_docs/data_search.mdx index 9f09caae8accd..736ab6888a419 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-02-05 +date: 2024-02-07 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 | |-------------------|-----------|------------------------|-----------------| -| 3235 | 31 | 2583 | 23 | +| 3237 | 31 | 2585 | 23 | ## Client diff --git a/api_docs/data_view_editor.mdx b/api_docs/data_view_editor.mdx index de9990bb83e10..a702fe304577c 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewEditor'] --- import dataViewEditorObj from './data_view_editor.devdocs.json'; diff --git a/api_docs/data_view_field_editor.mdx b/api_docs/data_view_field_editor.mdx index a665e1bf95211..4d81b7a67b294 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-02-05 +date: 2024-02-07 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 10d09bb5580bc..500962ad0ea2b 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-02-05 +date: 2024-02-07 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 4686208acd12e..b8995d9fca912 100644 --- a/api_docs/data_views.devdocs.json +++ b/api_docs/data_views.devdocs.json @@ -1045,7 +1045,7 @@ }, " | undefined; fixedInterval?: string[] | undefined; timeZone?: string[] | undefined; timeSeriesDimension?: boolean | undefined; timeSeriesMetric?: ", "MappingTimeSeriesMetricType", - " | undefined; shortDotsEnable?: boolean | undefined; isMapped?: boolean | undefined; parentName?: string | undefined; }" + " | undefined; shortDotsEnable?: boolean | undefined; isMapped?: boolean | undefined; parentName?: string | undefined; defaultFormatter?: string | undefined; }" ], "path": "src/plugins/data_views/common/fields/data_view_field.ts", "deprecated": false, @@ -1121,6 +1121,20 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "dataViews", + "id": "def-public.DataViewField.defaultFormatter", + "type": "string", + "tags": [], + "label": "defaultFormatter", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/data_views/common/fields/data_view_field.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "dataViews", "id": "def-public.DataViewField.runtimeField", @@ -1708,7 +1722,7 @@ "section": "def-common.IFieldSubType", "text": "IFieldSubType" }, - " | undefined; customLabel: string | undefined; }" + " | undefined; customLabel: string | undefined; defaultFormatter: string | undefined; }" ], "path": "src/plugins/data_views/common/fields/data_view_field.ts", "deprecated": false, @@ -6327,7 +6341,7 @@ }, " | undefined; fixedInterval?: string[] | undefined; timeZone?: string[] | undefined; timeSeriesDimension?: boolean | undefined; timeSeriesMetric?: ", "MappingTimeSeriesMetricType", - " | undefined; shortDotsEnable?: boolean | undefined; isMapped?: boolean | undefined; parentName?: string | undefined; }" + " | undefined; shortDotsEnable?: boolean | undefined; isMapped?: boolean | undefined; parentName?: string | undefined; defaultFormatter?: string | undefined; }" ], "path": "src/plugins/data_views/common/types.ts", "deprecated": false, @@ -10251,6 +10265,20 @@ "path": "src/plugins/data_views/server/fetcher/index_patterns_fetcher.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "dataViews", + "id": "def-server.FieldDescriptor.defaultFormatter", + "type": "string", + "tags": [], + "label": "defaultFormatter", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/data_views/server/fetcher/index_patterns_fetcher.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -10385,7 +10413,7 @@ }, " | undefined; fixedInterval?: string[] | undefined; timeZone?: string[] | undefined; timeSeriesDimension?: boolean | undefined; timeSeriesMetric?: ", "MappingTimeSeriesMetricType", - " | undefined; shortDotsEnable?: boolean | undefined; isMapped?: boolean | undefined; parentName?: string | undefined; }" + " | undefined; shortDotsEnable?: boolean | undefined; isMapped?: boolean | undefined; parentName?: string | undefined; defaultFormatter?: string | undefined; }" ], "path": "src/plugins/data_views/common/types.ts", "deprecated": false, @@ -13069,7 +13097,7 @@ }, " | undefined; fixedInterval?: string[] | undefined; timeZone?: string[] | undefined; timeSeriesDimension?: boolean | undefined; timeSeriesMetric?: ", "MappingTimeSeriesMetricType", - " | undefined; shortDotsEnable?: boolean | undefined; isMapped?: boolean | undefined; parentName?: string | undefined; }" + " | undefined; shortDotsEnable?: boolean | undefined; isMapped?: boolean | undefined; parentName?: string | undefined; defaultFormatter?: string | undefined; }" ], "path": "src/plugins/data_views/common/fields/data_view_field.ts", "deprecated": false, @@ -13145,6 +13173,20 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "dataViews", + "id": "def-common.DataViewField.defaultFormatter", + "type": "string", + "tags": [], + "label": "defaultFormatter", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/data_views/common/fields/data_view_field.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "dataViews", "id": "def-common.DataViewField.runtimeField", @@ -13732,7 +13774,7 @@ "section": "def-common.IFieldSubType", "text": "IFieldSubType" }, - " | undefined; customLabel: string | undefined; }" + " | undefined; customLabel: string | undefined; defaultFormatter: string | undefined; }" ], "path": "src/plugins/data_views/common/fields/data_view_field.ts", "deprecated": false, @@ -20951,7 +20993,7 @@ }, " | undefined; fixedInterval?: string[] | undefined; timeZone?: string[] | undefined; timeSeriesDimension?: boolean | undefined; timeSeriesMetric?: ", "MappingTimeSeriesMetricType", - " | undefined; shortDotsEnable?: boolean | undefined; isMapped?: boolean | undefined; parentName?: string | undefined; }" + " | undefined; shortDotsEnable?: boolean | undefined; isMapped?: boolean | undefined; parentName?: string | undefined; defaultFormatter?: string | undefined; }" ], "path": "src/plugins/data_views/common/types.ts", "deprecated": false, diff --git a/api_docs/data_views.mdx b/api_docs/data_views.mdx index 330a64f0b8449..2826aa5424d1e 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-02-05 +date: 2024-02-07 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 | |-------------------|-----------|------------------------|-----------------| -| 940 | 0 | 273 | 4 | +| 943 | 0 | 276 | 4 | ## Client diff --git a/api_docs/data_visualizer.mdx b/api_docs/data_visualizer.mdx index a7e1d0e9543a4..27766dc46e00d 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-02-05 +date: 2024-02-07 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 56687b3751bb3..285b2f4d7f7cb 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-02-05 +date: 2024-02-07 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 0ef89319e8460..4d3566baa5a61 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- diff --git a/api_docs/deprecations_by_plugin.mdx b/api_docs/deprecations_by_plugin.mdx index e50b875690e6e..6d81a57f9015d 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -1339,7 +1339,7 @@ migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/ | | [app.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/app/app.tsx#:~:text=KibanaThemeProvider), [app.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/app/app.tsx#:~:text=KibanaThemeProvider), [app.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/app/app.tsx#:~:text=KibanaThemeProvider) | - | | | [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [create_default_policy.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts#:~:text=mode), [create_default_policy.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode)+ 7 more | 8.8.0 | | | [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [create_default_policy.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts#:~:text=mode), [create_default_policy.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode)+ 7 more | 8.8.0 | -| | [query.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/query.ts#:~:text=license%24), [threshold.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/threshold/threshold.ts#:~:text=license%24) | 8.8.0 | +| | [create_threat_signal.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/threat_mapping/create_threat_signal.ts#:~:text=license%24), [create_event_signal.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/threat_mapping/create_event_signal.ts#:~:text=license%24), [query.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/query.ts#:~:text=license%24), [threshold.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/threshold/threshold.ts#:~:text=license%24) | 8.8.0 | | | [request_context_factory.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/request_context_factory.ts#:~:text=authc), [route.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/route.ts#:~:text=authc), [create_signals_migration_route.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/create_signals_migration_route.ts#:~:text=authc), [delete_signals_migration_route.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/delete_signals_migration_route.ts#:~:text=authc), [finalize_signals_migration_route.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/finalize_signals_migration_route.ts#:~:text=authc), [common.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/timeline/utils/common.ts#:~:text=authc) | - | | | [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) | - | diff --git a/api_docs/deprecations_by_team.mdx b/api_docs/deprecations_by_team.mdx index 9b364ca6f838f..1597b7565e31f 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -165,4 +165,4 @@ migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/ | --------|-------|-----------|-----------| | securitySolution | | [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [create_default_policy.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts#:~:text=mode), [create_default_policy.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode)+ 7 more | 8.8.0 | | securitySolution | | [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [create_default_policy.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts#:~:text=mode), [create_default_policy.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode)+ 7 more | 8.8.0 | -| securitySolution | | [query.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/query.ts#:~:text=license%24), [threshold.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/threshold/threshold.ts#:~:text=license%24) | 8.8.0 | \ No newline at end of file +| securitySolution | | [create_threat_signal.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/threat_mapping/create_threat_signal.ts#:~:text=license%24), [create_event_signal.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/threat_mapping/create_event_signal.ts#:~:text=license%24), [query.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/query.ts#:~:text=license%24), [threshold.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/threshold/threshold.ts#:~:text=license%24) | 8.8.0 | \ No newline at end of file diff --git a/api_docs/dev_tools.mdx b/api_docs/dev_tools.mdx index a2353d4af3e14..ebd0134451a37 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-02-05 +date: 2024-02-07 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 b12e043f1a2b3..0671ccf421cf3 100644 --- a/api_docs/discover.devdocs.json +++ b/api_docs/discover.devdocs.json @@ -24,10 +24,10 @@ }, { "parentPluginId": "discover", - "id": "def-public.LogExplorerTabs", + "id": "def-public.LogsExplorerTabs", "type": "Function", "tags": [], - "label": "LogExplorerTabs", + "label": "LogsExplorerTabs", "description": [], "signature": [ "React.ForwardRefExoticComponent<", @@ -35,8 +35,8 @@ "pluginId": "discover", "scope": "public", "docId": "kibDiscoverPluginApi", - "section": "def-public.LogExplorerTabsProps", - "text": "LogExplorerTabsProps" + "section": "def-public.LogsExplorerTabsProps", + "text": "LogsExplorerTabsProps" }, " & ", { @@ -48,14 +48,14 @@ }, " & React.RefAttributes<{}>>" ], - "path": "src/plugins/discover/public/components/log_explorer_tabs/index.ts", + "path": "src/plugins/discover/public/components/logs_explorer_tabs/index.ts", "deprecated": false, "trackAdoption": false, "returnComment": [], "children": [ { "parentPluginId": "discover", - "id": "def-public.LogExplorerTabs.$1", + "id": "def-public.LogsExplorerTabs.$1", "type": "Uncategorized", "tags": [], "label": "props", @@ -1163,18 +1163,18 @@ }, { "parentPluginId": "discover", - "id": "def-public.LogExplorerTabsProps", + "id": "def-public.LogsExplorerTabsProps", "type": "Interface", "tags": [], - "label": "LogExplorerTabsProps", + "label": "LogsExplorerTabsProps", "description": [], - "path": "src/plugins/discover/public/components/log_explorer_tabs/log_explorer_tabs.tsx", + "path": "src/plugins/discover/public/components/logs_explorer_tabs/logs_explorer_tabs.tsx", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "discover", - "id": "def-public.LogExplorerTabsProps.services", + "id": "def-public.LogsExplorerTabsProps.services", "type": "Object", "tags": [], "label": "services", @@ -1190,21 +1190,21 @@ }, " | undefined; }" ], - "path": "src/plugins/discover/public/components/log_explorer_tabs/log_explorer_tabs.tsx", + "path": "src/plugins/discover/public/components/logs_explorer_tabs/logs_explorer_tabs.tsx", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "discover", - "id": "def-public.LogExplorerTabsProps.selectedTab", + "id": "def-public.LogsExplorerTabsProps.selectedTab", "type": "CompoundType", "tags": [], "label": "selectedTab", "description": [], "signature": [ - "\"discover\" | \"log-explorer\"" + "\"discover\" | \"logs-explorer\"" ], - "path": "src/plugins/discover/public/components/log_explorer_tabs/log_explorer_tabs.tsx", + "path": "src/plugins/discover/public/components/logs_explorer_tabs/logs_explorer_tabs.tsx", "deprecated": false, "trackAdoption": false } @@ -1722,10 +1722,10 @@ }, { "parentPluginId": "discover", - "id": "def-public.DiscoverSetup.showLogExplorerTabs", + "id": "def-public.DiscoverSetup.showLogsExplorerTabs", "type": "Function", "tags": [], - "label": "showLogExplorerTabs", + "label": "showLogsExplorerTabs", "description": [], "signature": [ "() => void" @@ -2318,7 +2318,7 @@ }, ">" ], - "path": "src/plugins/discover/common/locator.ts", + "path": "src/plugins/discover/common/app_locator.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -2332,7 +2332,7 @@ "signature": [ "\"DISCOVER_APP_LOCATOR\"" ], - "path": "src/plugins/discover/common/locator.ts", + "path": "src/plugins/discover/common/app_locator.ts", "deprecated": false, "trackAdoption": false }, @@ -2346,7 +2346,7 @@ "signature": [ "any" ], - "path": "src/plugins/discover/common/locator.ts", + "path": "src/plugins/discover/common/app_locator.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -2360,7 +2360,7 @@ "signature": [ "DiscoverAppLocatorDependencies" ], - "path": "src/plugins/discover/common/locator.ts", + "path": "src/plugins/discover/common/app_locator.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -2385,10 +2385,16 @@ "text": "DiscoverAppLocatorParams" }, ") => Promise<{ app: string; path: string; state: ", - "MainHistoryLocationState", + { + "pluginId": "discover", + "scope": "common", + "docId": "kibDiscoverPluginApi", + "section": "def-common.MainHistoryLocationState", + "text": "MainHistoryLocationState" + }, "; }>" ], - "path": "src/plugins/discover/common/locator.ts", + "path": "src/plugins/discover/common/app_locator.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -2408,7 +2414,7 @@ "text": "DiscoverAppLocatorParams" } ], - "path": "src/plugins/discover/common/locator.ts", + "path": "src/plugins/discover/common/app_locator.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -2418,6 +2424,110 @@ } ], "initialIsOpen": false + }, + { + "parentPluginId": "discover", + "id": "def-common.DiscoverESQLLocatorDefinition", + "type": "Class", + "tags": [], + "label": "DiscoverESQLLocatorDefinition", + "description": [], + "signature": [ + { + "pluginId": "discover", + "scope": "common", + "docId": "kibDiscoverPluginApi", + "section": "def-common.DiscoverESQLLocatorDefinition", + "text": "DiscoverESQLLocatorDefinition" + }, + " implements ", + { + "pluginId": "share", + "scope": "common", + "docId": "kibSharePluginApi", + "section": "def-common.LocatorDefinition", + "text": "LocatorDefinition" + }, + "<", + { + "pluginId": "@kbn/utility-types", + "scope": "common", + "docId": "kibKbnUtilityTypesPluginApi", + "section": "def-common.SerializableRecord", + "text": "SerializableRecord" + }, + ">" + ], + "path": "src/plugins/discover/common/esql_locator.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "discover", + "id": "def-common.DiscoverESQLLocatorDefinition.id", + "type": "string", + "tags": [], + "label": "id", + "description": [], + "signature": [ + "\"DISCOVER_ESQL_LOCATOR\"" + ], + "path": "src/plugins/discover/common/esql_locator.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "discover", + "id": "def-common.DiscoverESQLLocatorDefinition.Unnamed", + "type": "Function", + "tags": [], + "label": "Constructor", + "description": [], + "signature": [ + "any" + ], + "path": "src/plugins/discover/common/esql_locator.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "discover", + "id": "def-common.DiscoverESQLLocatorDefinition.Unnamed.$1", + "type": "Object", + "tags": [], + "label": "deps", + "description": [], + "signature": [ + "DiscoverESQLLocatorDependencies" + ], + "path": "src/plugins/discover/common/esql_locator.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "discover", + "id": "def-common.DiscoverESQLLocatorDefinition.getLocation", + "type": "Function", + "tags": [], + "label": "getLocation", + "description": [], + "signature": [ + "() => Promise<", + "KibanaLocation", + ">" + ], + "path": "src/plugins/discover/common/esql_locator.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + } + ], + "initialIsOpen": false } ], "functions": [], @@ -2446,7 +2556,7 @@ "text": "SerializableRecord" } ], - "path": "src/plugins/discover/common/locator.ts", + "path": "src/plugins/discover/common/app_locator.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -2462,7 +2572,7 @@ "signature": [ "string | undefined" ], - "path": "src/plugins/discover/common/locator.ts", + "path": "src/plugins/discover/common/app_locator.ts", "deprecated": false, "trackAdoption": false }, @@ -2478,7 +2588,7 @@ "signature": [ "string | undefined" ], - "path": "src/plugins/discover/common/locator.ts", + "path": "src/plugins/discover/common/app_locator.ts", "deprecated": false, "trackAdoption": false }, @@ -2496,7 +2606,7 @@ "signature": [ "string | undefined" ], - "path": "src/plugins/discover/common/locator.ts", + "path": "src/plugins/discover/common/app_locator.ts", "deprecated": true, "trackAdoption": false, "references": [ @@ -2563,7 +2673,7 @@ }, " | undefined" ], - "path": "src/plugins/discover/common/locator.ts", + "path": "src/plugins/discover/common/app_locator.ts", "deprecated": false, "trackAdoption": false }, @@ -2586,7 +2696,7 @@ }, " | undefined" ], - "path": "src/plugins/discover/common/locator.ts", + "path": "src/plugins/discover/common/app_locator.ts", "deprecated": false, "trackAdoption": false }, @@ -2618,7 +2728,7 @@ }, ") | undefined" ], - "path": "src/plugins/discover/common/locator.ts", + "path": "src/plugins/discover/common/app_locator.ts", "deprecated": false, "trackAdoption": false }, @@ -2641,7 +2751,7 @@ }, "[] | undefined" ], - "path": "src/plugins/discover/common/locator.ts", + "path": "src/plugins/discover/common/app_locator.ts", "deprecated": false, "trackAdoption": false }, @@ -2672,7 +2782,7 @@ }, " | undefined" ], - "path": "src/plugins/discover/common/locator.ts", + "path": "src/plugins/discover/common/app_locator.ts", "deprecated": false, "trackAdoption": false }, @@ -2688,7 +2798,7 @@ "signature": [ "boolean | undefined" ], - "path": "src/plugins/discover/common/locator.ts", + "path": "src/plugins/discover/common/app_locator.ts", "deprecated": false, "trackAdoption": false }, @@ -2704,7 +2814,7 @@ "signature": [ "string | undefined" ], - "path": "src/plugins/discover/common/locator.ts", + "path": "src/plugins/discover/common/app_locator.ts", "deprecated": false, "trackAdoption": false }, @@ -2720,7 +2830,7 @@ "signature": [ "string[] | undefined" ], - "path": "src/plugins/discover/common/locator.ts", + "path": "src/plugins/discover/common/app_locator.ts", "deprecated": false, "trackAdoption": false }, @@ -2743,7 +2853,7 @@ }, " | undefined" ], - "path": "src/plugins/discover/common/locator.ts", + "path": "src/plugins/discover/common/app_locator.ts", "deprecated": false, "trackAdoption": false }, @@ -2759,7 +2869,7 @@ "signature": [ "string | undefined" ], - "path": "src/plugins/discover/common/locator.ts", + "path": "src/plugins/discover/common/app_locator.ts", "deprecated": false, "trackAdoption": false }, @@ -2775,7 +2885,7 @@ "signature": [ "string[][] | undefined" ], - "path": "src/plugins/discover/common/locator.ts", + "path": "src/plugins/discover/common/app_locator.ts", "deprecated": false, "trackAdoption": false }, @@ -2791,7 +2901,7 @@ "signature": [ "string | undefined" ], - "path": "src/plugins/discover/common/locator.ts", + "path": "src/plugins/discover/common/app_locator.ts", "deprecated": false, "trackAdoption": false }, @@ -2808,7 +2918,7 @@ "VIEW_MODE", " | undefined" ], - "path": "src/plugins/discover/common/locator.ts", + "path": "src/plugins/discover/common/app_locator.ts", "deprecated": false, "trackAdoption": false }, @@ -2824,7 +2934,7 @@ "signature": [ "boolean | undefined" ], - "path": "src/plugins/discover/common/locator.ts", + "path": "src/plugins/discover/common/app_locator.ts", "deprecated": false, "trackAdoption": false }, @@ -2840,7 +2950,7 @@ "signature": [ "string | undefined" ], - "path": "src/plugins/discover/common/locator.ts", + "path": "src/plugins/discover/common/app_locator.ts", "deprecated": false, "trackAdoption": false }, @@ -2856,7 +2966,7 @@ "signature": [ "boolean | undefined" ], - "path": "src/plugins/discover/common/locator.ts", + "path": "src/plugins/discover/common/app_locator.ts", "deprecated": false, "trackAdoption": false }, @@ -2872,7 +2982,58 @@ "signature": [ "string | undefined" ], - "path": "src/plugins/discover/common/locator.ts", + "path": "src/plugins/discover/common/app_locator.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "discover", + "id": "def-common.MainHistoryLocationState", + "type": "Interface", + "tags": [], + "label": "MainHistoryLocationState", + "description": [ + "\nLocation state of scoped history (history instance of Kibana Platform application service)" + ], + "path": "src/plugins/discover/common/app_locator.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "discover", + "id": "def-common.MainHistoryLocationState.dataViewSpec", + "type": "Object", + "tags": [], + "label": "dataViewSpec", + "description": [], + "signature": [ + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataViewSpec", + "text": "DataViewSpec" + }, + " | undefined" + ], + "path": "src/plugins/discover/common/app_locator.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "discover", + "id": "def-common.MainHistoryLocationState.isAlertResults", + "type": "CompoundType", + "tags": [], + "label": "isAlertResults", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/discover/common/app_locator.ts", "deprecated": false, "trackAdoption": false } @@ -2907,7 +3068,7 @@ "signature": [ "\"DISCOVER_APP_LOCATOR\"" ], - "path": "src/plugins/discover/common/locator.ts", + "path": "src/plugins/discover/common/app_locator.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -2937,7 +3098,58 @@ }, ">" ], - "path": "src/plugins/discover/common/locator.ts", + "path": "src/plugins/discover/common/app_locator.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "discover", + "id": "def-common.DiscoverESQLLocator", + "type": "Type", + "tags": [], + "label": "DiscoverESQLLocator", + "description": [], + "signature": [ + { + "pluginId": "share", + "scope": "common", + "docId": "kibSharePluginApi", + "section": "def-common.LocatorPublic", + "text": "LocatorPublic" + }, + "<", + { + "pluginId": "@kbn/utility-types", + "scope": "common", + "docId": "kibKbnUtilityTypesPluginApi", + "section": "def-common.SerializableRecord", + "text": "SerializableRecord" + }, + ">" + ], + "path": "src/plugins/discover/common/esql_locator.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "discover", + "id": "def-common.DiscoverESQLLocatorParams", + "type": "Type", + "tags": [], + "label": "DiscoverESQLLocatorParams", + "description": [], + "signature": [ + { + "pluginId": "@kbn/utility-types", + "scope": "common", + "docId": "kibKbnUtilityTypesPluginApi", + "section": "def-common.SerializableRecord", + "text": "SerializableRecord" + } + ], + "path": "src/plugins/discover/common/esql_locator.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false diff --git a/api_docs/discover.mdx b/api_docs/discover.mdx index ddac1cb2440f3..7e487fd29fae8 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discover'] --- import discoverObj from './discover.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/k | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 141 | 0 | 95 | 22 | +| 151 | 0 | 104 | 22 | ## Client diff --git a/api_docs/discover_enhanced.mdx b/api_docs/discover_enhanced.mdx index 93a342db836d8..214e4934f0546 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discoverEnhanced'] --- import discoverEnhancedObj from './discover_enhanced.devdocs.json'; diff --git a/api_docs/ecs_data_quality_dashboard.mdx b/api_docs/ecs_data_quality_dashboard.mdx index 0b55cc0a6112f..6a80d575ecbd8 100644 --- a/api_docs/ecs_data_quality_dashboard.mdx +++ b/api_docs/ecs_data_quality_dashboard.mdx @@ -8,14 +8,14 @@ slug: /kibana-dev-docs/api/ecsDataQualityDashboard title: "ecsDataQualityDashboard" image: https://source.unsplash.com/400x175/?github description: API docs for the ecsDataQualityDashboard plugin -date: 2024-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ecsDataQualityDashboard'] --- import ecsDataQualityDashboardObj from './ecs_data_quality_dashboard.devdocs.json'; APIs used to assess the quality of data in Elasticsearch indexes -Contact [@elastic/security-threat-hunting-investigations](https://github.com/orgs/elastic/teams/security-threat-hunting-investigations) for questions regarding this plugin. +Contact [@elastic/security-threat-hunting-explore](https://github.com/orgs/elastic/teams/security-threat-hunting-explore) for questions regarding this plugin. **Code health stats** diff --git a/api_docs/elastic_assistant.mdx b/api_docs/elastic_assistant.mdx index bb7e03253016f..a56158deb96f4 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-02-05 +date: 2024-02-07 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 eeecb696c191e..cbd3e011180d6 100644 --- a/api_docs/embeddable.devdocs.json +++ b/api_docs/embeddable.devdocs.json @@ -5875,6 +5875,170 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "embeddable", + "id": "def-public.Embeddable.savedObjectId", + "type": "Object", + "tags": [], + "label": "savedObjectId", + "description": [], + "signature": [ + "{ readonly value: string | undefined; source: ", + "Observable", + " | undefined; error: (err: any) => void; forEach: { (next: (value: string | undefined) => void): Promise; (next: (value: string | undefined) => void, promiseCtor: PromiseConstructorLike): Promise; }; getValue: () => string | undefined; pipe: { (): ", + "Observable", + "; (op1: ", + "OperatorFunction", + "): ", + "Observable", + "; (op1: ", + "OperatorFunction", + ", op2: ", + "OperatorFunction", + "): ", + "Observable", + "; (op1: ", + "OperatorFunction", + ", op2: ", + "OperatorFunction", + ", op3: ", + "OperatorFunction", + "): ", + "Observable", + "; (op1: ", + "OperatorFunction", + ", op2: ", + "OperatorFunction", + ", op3: ", + "OperatorFunction", + ", op4: ", + "OperatorFunction", + "): ", + "Observable", + "; (op1: ", + "OperatorFunction", + ", op2: ", + "OperatorFunction", + ", op3: ", + "OperatorFunction", + ", op4: ", + "OperatorFunction", + ", op5: ", + "OperatorFunction", + "): ", + "Observable", + "; (op1: ", + "OperatorFunction", + ", op2: ", + "OperatorFunction", + ", op3: ", + "OperatorFunction", + ", op4: ", + "OperatorFunction", + ", op5: ", + "OperatorFunction", + ", op6: ", + "OperatorFunction", + "): ", + "Observable", + "; (op1: ", + "OperatorFunction", + ", op2: ", + "OperatorFunction", + ", op3: ", + "OperatorFunction", + ", op4: ", + "OperatorFunction", + ", op5: ", + "OperatorFunction", + ", op6: ", + "OperatorFunction", + ", op7: ", + "OperatorFunction", + "): ", + "Observable", + "; (op1: ", + "OperatorFunction", + ", op2: ", + "OperatorFunction", + ", op3: ", + "OperatorFunction", + ", op4: ", + "OperatorFunction", + ", op5: ", + "OperatorFunction", + ", op6: ", + "OperatorFunction", + ", op7: ", + "OperatorFunction", + ", op8: ", + "OperatorFunction", + "): ", + "Observable", + "; (op1: ", + "OperatorFunction", + ", op2: ", + "OperatorFunction", + ", op3: ", + "OperatorFunction", + ", op4: ", + "OperatorFunction", + ", op5: ", + "OperatorFunction", + ", op6: ", + "OperatorFunction", + ", op7: ", + "OperatorFunction", + ", op8: ", + "OperatorFunction", + ", op9: ", + "OperatorFunction", + "): ", + "Observable", + "; (op1: ", + "OperatorFunction", + ", op2: ", + "OperatorFunction", + ", op3: ", + "OperatorFunction", + ", op4: ", + "OperatorFunction", + ", op5: ", + "OperatorFunction", + ", op6: ", + "OperatorFunction", + ", op7: ", + "OperatorFunction", + ", op8: ", + "OperatorFunction", + ", op9: ", + "OperatorFunction", + ", ...operations: ", + "OperatorFunction", + "[]): ", + "Observable", + "; }; complete: () => void; closed: boolean; observers: ", + "Observer", + "[]; isStopped: boolean; hasError: boolean; thrownError: any; lift: (operator: ", + "Operator", + ") => ", + "Observable", + "; unsubscribe: () => void; readonly observed: boolean; asObservable: () => ", + "Observable", + "; operator: ", + "Operator", + " | undefined; subscribe: { (observerOrNext?: Partial<", + "Observer", + "> | ((value: string | undefined) => void) | undefined): ", + "Subscription", + "; (next?: ((value: string | undefined) => void) | null | undefined, error?: ((error: any) => void) | null | undefined, complete?: (() => void) | null | undefined): ", + "Subscription", + "; }; toPromise: { (): Promise; (PromiseCtor: PromiseConstructor): Promise; (PromiseCtor: PromiseConstructorLike): Promise; }; }" + ], + "path": "src/plugins/embeddable/public/lib/embeddables/embeddable.tsx", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "embeddable", "id": "def-public.Embeddable.getEditHref", @@ -8041,63 +8205,14 @@ "section": "def-public.ChartActionContext", "text": "ChartActionContext" }, - "<", - { - "pluginId": "embeddable", - "scope": "public", - "docId": "kibEmbeddablePluginApi", - "section": "def-public.IEmbeddable", - "text": "IEmbeddable" - }, - "<", - { - "pluginId": "embeddable", - "scope": "common", - "docId": "kibEmbeddablePluginApi", - "section": "def-common.EmbeddableInput", - "text": "EmbeddableInput" - }, - ", ", - { - "pluginId": "embeddable", - "scope": "public", - "docId": "kibEmbeddablePluginApi", - "section": "def-public.EmbeddableOutput", - "text": "EmbeddableOutput" - }, - ", any>>) => context is ", + ") => context is ", { "pluginId": "embeddable", "scope": "public", "docId": "kibEmbeddablePluginApi", "section": "def-public.MultiValueClickContext", "text": "MultiValueClickContext" - }, - "<", - { - "pluginId": "embeddable", - "scope": "public", - "docId": "kibEmbeddablePluginApi", - "section": "def-public.IEmbeddable", - "text": "IEmbeddable" - }, - "<", - { - "pluginId": "embeddable", - "scope": "common", - "docId": "kibEmbeddablePluginApi", - "section": "def-common.EmbeddableInput", - "text": "EmbeddableInput" - }, - ", ", - { - "pluginId": "embeddable", - "scope": "public", - "docId": "kibEmbeddablePluginApi", - "section": "def-public.EmbeddableOutput", - "text": "EmbeddableOutput" - }, - ", any>>" + } ], "path": "src/plugins/embeddable/public/lib/triggers/triggers.ts", "deprecated": false, @@ -8117,32 +8232,7 @@ "docId": "kibEmbeddablePluginApi", "section": "def-public.ChartActionContext", "text": "ChartActionContext" - }, - "<", - { - "pluginId": "embeddable", - "scope": "public", - "docId": "kibEmbeddablePluginApi", - "section": "def-public.IEmbeddable", - "text": "IEmbeddable" - }, - "<", - { - "pluginId": "embeddable", - "scope": "common", - "docId": "kibEmbeddablePluginApi", - "section": "def-common.EmbeddableInput", - "text": "EmbeddableInput" - }, - ", ", - { - "pluginId": "embeddable", - "scope": "public", - "docId": "kibEmbeddablePluginApi", - "section": "def-public.EmbeddableOutput", - "text": "EmbeddableOutput" - }, - ", any>>" + } ], "path": "src/plugins/embeddable/public/lib/triggers/triggers.ts", "deprecated": false, @@ -8169,68 +8259,19 @@ "section": "def-public.ChartActionContext", "text": "ChartActionContext" }, - "<", + ") => context is ", { "pluginId": "embeddable", "scope": "public", "docId": "kibEmbeddablePluginApi", - "section": "def-public.IEmbeddable", - "text": "IEmbeddable" - }, - "<", - { - "pluginId": "embeddable", - "scope": "common", - "docId": "kibEmbeddablePluginApi", - "section": "def-common.EmbeddableInput", - "text": "EmbeddableInput" - }, - ", ", - { - "pluginId": "embeddable", - "scope": "public", - "docId": "kibEmbeddablePluginApi", - "section": "def-public.EmbeddableOutput", - "text": "EmbeddableOutput" - }, - ", any>>) => context is ", - { - "pluginId": "embeddable", - "scope": "public", - "docId": "kibEmbeddablePluginApi", - "section": "def-public.RangeSelectContext", - "text": "RangeSelectContext" - }, - "<", - { - "pluginId": "embeddable", - "scope": "public", - "docId": "kibEmbeddablePluginApi", - "section": "def-public.IEmbeddable", - "text": "IEmbeddable" - }, - "<", - { - "pluginId": "embeddable", - "scope": "common", - "docId": "kibEmbeddablePluginApi", - "section": "def-common.EmbeddableInput", - "text": "EmbeddableInput" - }, - ", ", - { - "pluginId": "embeddable", - "scope": "public", - "docId": "kibEmbeddablePluginApi", - "section": "def-public.EmbeddableOutput", - "text": "EmbeddableOutput" - }, - ", any>>" - ], - "path": "src/plugins/embeddable/public/lib/triggers/triggers.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ + "section": "def-public.RangeSelectContext", + "text": "RangeSelectContext" + } + ], + "path": "src/plugins/embeddable/public/lib/triggers/triggers.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ { "parentPluginId": "embeddable", "id": "def-public.isRangeSelectTriggerContext.$1", @@ -8245,32 +8286,7 @@ "docId": "kibEmbeddablePluginApi", "section": "def-public.ChartActionContext", "text": "ChartActionContext" - }, - "<", - { - "pluginId": "embeddable", - "scope": "public", - "docId": "kibEmbeddablePluginApi", - "section": "def-public.IEmbeddable", - "text": "IEmbeddable" - }, - "<", - { - "pluginId": "embeddable", - "scope": "common", - "docId": "kibEmbeddablePluginApi", - "section": "def-common.EmbeddableInput", - "text": "EmbeddableInput" - }, - ", ", - { - "pluginId": "embeddable", - "scope": "public", - "docId": "kibEmbeddablePluginApi", - "section": "def-public.EmbeddableOutput", - "text": "EmbeddableOutput" - }, - ", any>>" + } ], "path": "src/plugins/embeddable/public/lib/triggers/triggers.ts", "deprecated": false, @@ -8330,31 +8346,7 @@ "section": "def-public.ChartActionContext", "text": "ChartActionContext" }, - "<", - { - "pluginId": "embeddable", - "scope": "public", - "docId": "kibEmbeddablePluginApi", - "section": "def-public.IEmbeddable", - "text": "IEmbeddable" - }, - "<", - { - "pluginId": "embeddable", - "scope": "common", - "docId": "kibEmbeddablePluginApi", - "section": "def-common.EmbeddableInput", - "text": "EmbeddableInput" - }, - ", ", - { - "pluginId": "embeddable", - "scope": "public", - "docId": "kibEmbeddablePluginApi", - "section": "def-public.EmbeddableOutput", - "text": "EmbeddableOutput" - }, - ", any>>) => context is ", + ") => context is ", { "pluginId": "@kbn/ui-actions-browser", "scope": "common", @@ -8381,32 +8373,7 @@ "docId": "kibEmbeddablePluginApi", "section": "def-public.ChartActionContext", "text": "ChartActionContext" - }, - "<", - { - "pluginId": "embeddable", - "scope": "public", - "docId": "kibEmbeddablePluginApi", - "section": "def-public.IEmbeddable", - "text": "IEmbeddable" - }, - "<", - { - "pluginId": "embeddable", - "scope": "common", - "docId": "kibEmbeddablePluginApi", - "section": "def-common.EmbeddableInput", - "text": "EmbeddableInput" - }, - ", ", - { - "pluginId": "embeddable", - "scope": "public", - "docId": "kibEmbeddablePluginApi", - "section": "def-public.EmbeddableOutput", - "text": "EmbeddableOutput" - }, - ", any>>" + } ], "path": "src/plugins/embeddable/public/lib/triggers/triggers.ts", "deprecated": false, @@ -8496,63 +8463,14 @@ "section": "def-public.ChartActionContext", "text": "ChartActionContext" }, - "<", - { - "pluginId": "embeddable", - "scope": "public", - "docId": "kibEmbeddablePluginApi", - "section": "def-public.IEmbeddable", - "text": "IEmbeddable" - }, - "<", - { - "pluginId": "embeddable", - "scope": "common", - "docId": "kibEmbeddablePluginApi", - "section": "def-common.EmbeddableInput", - "text": "EmbeddableInput" - }, - ", ", - { - "pluginId": "embeddable", - "scope": "public", - "docId": "kibEmbeddablePluginApi", - "section": "def-public.EmbeddableOutput", - "text": "EmbeddableOutput" - }, - ", any>>) => context is ", + ") => context is ", { "pluginId": "embeddable", "scope": "public", "docId": "kibEmbeddablePluginApi", "section": "def-public.ValueClickContext", "text": "ValueClickContext" - }, - "<", - { - "pluginId": "embeddable", - "scope": "public", - "docId": "kibEmbeddablePluginApi", - "section": "def-public.IEmbeddable", - "text": "IEmbeddable" - }, - "<", - { - "pluginId": "embeddable", - "scope": "common", - "docId": "kibEmbeddablePluginApi", - "section": "def-common.EmbeddableInput", - "text": "EmbeddableInput" - }, - ", ", - { - "pluginId": "embeddable", - "scope": "public", - "docId": "kibEmbeddablePluginApi", - "section": "def-public.EmbeddableOutput", - "text": "EmbeddableOutput" - }, - ", any>>" + } ], "path": "src/plugins/embeddable/public/lib/triggers/triggers.ts", "deprecated": false, @@ -8572,32 +8490,7 @@ "docId": "kibEmbeddablePluginApi", "section": "def-public.ChartActionContext", "text": "ChartActionContext" - }, - "<", - { - "pluginId": "embeddable", - "scope": "public", - "docId": "kibEmbeddablePluginApi", - "section": "def-public.IEmbeddable", - "text": "IEmbeddable" - }, - "<", - { - "pluginId": "embeddable", - "scope": "common", - "docId": "kibEmbeddablePluginApi", - "section": "def-common.EmbeddableInput", - "text": "EmbeddableInput" - }, - ", ", - { - "pluginId": "embeddable", - "scope": "public", - "docId": "kibEmbeddablePluginApi", - "section": "def-public.EmbeddableOutput", - "text": "EmbeddableOutput" - }, - ", any>>" + } ], "path": "src/plugins/embeddable/public/lib/triggers/triggers.ts", "deprecated": false, @@ -9683,66 +9576,6 @@ ], "initialIsOpen": false }, - { - "parentPluginId": "embeddable", - "id": "def-public.CellValueContext", - "type": "Interface", - "tags": [], - "label": "CellValueContext", - "description": [], - "signature": [ - { - "pluginId": "embeddable", - "scope": "public", - "docId": "kibEmbeddablePluginApi", - "section": "def-public.CellValueContext", - "text": "CellValueContext" - }, - "" - ], - "path": "src/plugins/embeddable/public/lib/triggers/triggers.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "embeddable", - "id": "def-public.CellValueContext.embeddable", - "type": "Uncategorized", - "tags": [], - "label": "embeddable", - "description": [], - "signature": [ - "T" - ], - "path": "src/plugins/embeddable/public/lib/triggers/triggers.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "embeddable", - "id": "def-public.CellValueContext.data", - "type": "Array", - "tags": [], - "label": "data", - "description": [], - "signature": [ - "{ value?: any; eventId?: string | undefined; columnMeta?: ", - { - "pluginId": "expressions", - "scope": "common", - "docId": "kibExpressionsPluginApi", - "section": "def-common.DatatableColumnMeta", - "text": "DatatableColumnMeta" - }, - " | undefined; }[]" - ], - "path": "src/plugins/embeddable/public/lib/triggers/triggers.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, { "parentPluginId": "embeddable", "id": "def-public.ContainerInput", @@ -12827,75 +12660,7 @@ }, { "parentPluginId": "embeddable", - "id": "def-public.MultiValueClickContext", - "type": "Interface", - "tags": [], - "label": "MultiValueClickContext", - "description": [], - "signature": [ - { - "pluginId": "embeddable", - "scope": "public", - "docId": "kibEmbeddablePluginApi", - "section": "def-public.MultiValueClickContext", - "text": "MultiValueClickContext" - }, - "" - ], - "path": "src/plugins/embeddable/public/lib/triggers/triggers.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "embeddable", - "id": "def-public.MultiValueClickContext.embeddable", - "type": "Uncategorized", - "tags": [], - "label": "embeddable", - "description": [], - "signature": [ - "T | undefined" - ], - "path": "src/plugins/embeddable/public/lib/triggers/triggers.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "embeddable", - "id": "def-public.MultiValueClickContext.data", - "type": "Object", - "tags": [], - "label": "data", - "description": [], - "signature": [ - "{ data: { table: Pick<", - { - "pluginId": "expressions", - "scope": "common", - "docId": "kibExpressionsPluginApi", - "section": "def-common.Datatable", - "text": "Datatable" - }, - ", \"rows\" | \"columns\">; cells: { column: number; row: number; }[]; relation?: ", - { - "pluginId": "@kbn/es-query", - "scope": "common", - "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.BooleanRelation", - "text": "BooleanRelation" - }, - " | undefined; }[]; timeFieldName?: string | undefined; negate?: boolean | undefined; }" - ], - "path": "src/plugins/embeddable/public/lib/triggers/triggers.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "embeddable", - "id": "def-public.OutputSpec", + "id": "def-public.OutputSpec", "type": "Interface", "tags": [], "label": "OutputSpec", @@ -13049,66 +12814,6 @@ ], "initialIsOpen": false }, - { - "parentPluginId": "embeddable", - "id": "def-public.RangeSelectContext", - "type": "Interface", - "tags": [], - "label": "RangeSelectContext", - "description": [], - "signature": [ - { - "pluginId": "embeddable", - "scope": "public", - "docId": "kibEmbeddablePluginApi", - "section": "def-public.RangeSelectContext", - "text": "RangeSelectContext" - }, - "" - ], - "path": "src/plugins/embeddable/public/lib/triggers/triggers.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "embeddable", - "id": "def-public.RangeSelectContext.embeddable", - "type": "Uncategorized", - "tags": [], - "label": "embeddable", - "description": [], - "signature": [ - "T | undefined" - ], - "path": "src/plugins/embeddable/public/lib/triggers/triggers.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "embeddable", - "id": "def-public.RangeSelectContext.data", - "type": "Object", - "tags": [], - "label": "data", - "description": [], - "signature": [ - "{ table: ", - { - "pluginId": "expressions", - "scope": "common", - "docId": "kibExpressionsPluginApi", - "section": "def-common.Datatable", - "text": "Datatable" - }, - "; column: number; range: number[]; timeFieldName?: string | undefined; }" - ], - "path": "src/plugins/embeddable/public/lib/triggers/triggers.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, { "parentPluginId": "embeddable", "id": "def-public.ReactEmbeddableFactory", @@ -13477,66 +13182,6 @@ } ], "initialIsOpen": false - }, - { - "parentPluginId": "embeddable", - "id": "def-public.ValueClickContext", - "type": "Interface", - "tags": [], - "label": "ValueClickContext", - "description": [], - "signature": [ - { - "pluginId": "embeddable", - "scope": "public", - "docId": "kibEmbeddablePluginApi", - "section": "def-public.ValueClickContext", - "text": "ValueClickContext" - }, - "" - ], - "path": "src/plugins/embeddable/public/lib/triggers/triggers.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "embeddable", - "id": "def-public.ValueClickContext.embeddable", - "type": "Uncategorized", - "tags": [], - "label": "embeddable", - "description": [], - "signature": [ - "T | undefined" - ], - "path": "src/plugins/embeddable/public/lib/triggers/triggers.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "embeddable", - "id": "def-public.ValueClickContext.data", - "type": "Object", - "tags": [], - "label": "data", - "description": [], - "signature": [ - "{ data: { table: Pick<", - { - "pluginId": "expressions", - "scope": "common", - "docId": "kibExpressionsPluginApi", - "section": "def-common.Datatable", - "text": "Datatable" - }, - ", \"rows\" | \"columns\">; column: number; row: number; value: any; }[]; timeFieldName?: string | undefined; negate?: boolean | undefined; }" - ], - "path": "src/plugins/embeddable/public/lib/triggers/triggers.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false } ], "enums": [ @@ -13586,6 +13231,37 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "embeddable", + "id": "def-public.CellValueContext", + "type": "Type", + "tags": [], + "label": "CellValueContext", + "description": [], + "signature": [ + "Partial<", + { + "pluginId": "@kbn/presentation-publishing", + "scope": "common", + "docId": "kibKbnPresentationPublishingPluginApi", + "section": "def-common.EmbeddableApiContext", + "text": "EmbeddableApiContext" + }, + "> & { data: { value?: any; eventId?: string | undefined; columnMeta?: ", + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.DatatableColumnMeta", + "text": "DatatableColumnMeta" + }, + " | undefined; }[]; }" + ], + "path": "src/plugins/embeddable/public/lib/triggers/triggers.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "embeddable", "id": "def-public.ChartActionContext", @@ -13601,23 +13277,23 @@ "section": "def-public.ValueClickContext", "text": "ValueClickContext" }, - " | ", + " | ", { "pluginId": "embeddable", "scope": "public", "docId": "kibEmbeddablePluginApi", - "section": "def-public.MultiValueClickContext", - "text": "MultiValueClickContext" + "section": "def-public.RangeSelectContext", + "text": "RangeSelectContext" }, - " | ", + " | ", { "pluginId": "embeddable", "scope": "public", "docId": "kibEmbeddablePluginApi", - "section": "def-public.RangeSelectContext", - "text": "RangeSelectContext" + "section": "def-public.MultiValueClickContext", + "text": "MultiValueClickContext" }, - " | ", + " | ", { "pluginId": "@kbn/ui-actions-browser", "scope": "common", @@ -13877,6 +13553,45 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "embeddable", + "id": "def-public.MultiValueClickContext", + "type": "Type", + "tags": [], + "label": "MultiValueClickContext", + "description": [], + "signature": [ + "Partial<", + { + "pluginId": "@kbn/presentation-publishing", + "scope": "common", + "docId": "kibKbnPresentationPublishingPluginApi", + "section": "def-common.EmbeddableApiContext", + "text": "EmbeddableApiContext" + }, + "> & { data: { data: { table: Pick<", + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.Datatable", + "text": "Datatable" + }, + ", \"rows\" | \"columns\">; cells: { column: number; row: number; }[]; relation?: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.BooleanRelation", + "text": "BooleanRelation" + }, + " | undefined; }[]; timeFieldName?: string | undefined; negate?: boolean | undefined; }; }" + ], + "path": "src/plugins/embeddable/public/lib/triggers/triggers.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "embeddable", "id": "def-public.PANEL_BADGE_TRIGGER", @@ -13922,6 +13637,37 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "embeddable", + "id": "def-public.RangeSelectContext", + "type": "Type", + "tags": [], + "label": "RangeSelectContext", + "description": [], + "signature": [ + "Partial<", + { + "pluginId": "@kbn/presentation-publishing", + "scope": "common", + "docId": "kibKbnPresentationPublishingPluginApi", + "section": "def-common.EmbeddableApiContext", + "text": "EmbeddableApiContext" + }, + "> & { data: { table: ", + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.Datatable", + "text": "Datatable" + }, + "; column: number; range: number[]; timeFieldName?: string | undefined; }; }" + ], + "path": "src/plugins/embeddable/public/lib/triggers/triggers.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "embeddable", "id": "def-public.ReactEmbeddable", @@ -14045,6 +13791,37 @@ "deprecated": false, "trackAdoption": false, "initialIsOpen": false + }, + { + "parentPluginId": "embeddable", + "id": "def-public.ValueClickContext", + "type": "Type", + "tags": [], + "label": "ValueClickContext", + "description": [], + "signature": [ + "Partial<", + { + "pluginId": "@kbn/presentation-publishing", + "scope": "common", + "docId": "kibKbnPresentationPublishingPluginApi", + "section": "def-common.EmbeddableApiContext", + "text": "EmbeddableApiContext" + }, + "> & { data: { data: { table: Pick<", + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.Datatable", + "text": "Datatable" + }, + ", \"rows\" | \"columns\">; column: number; row: number; value: any; }[]; timeFieldName?: string | undefined; negate?: boolean | undefined; }; }" + ], + "path": "src/plugins/embeddable/public/lib/triggers/triggers.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false } ], "objects": [ diff --git a/api_docs/embeddable.mdx b/api_docs/embeddable.mdx index fc7e5a981c139..443871d9e9a13 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-02-05 +date: 2024-02-07 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 | |-------------------|-----------|------------------------|-----------------| -| 564 | 1 | 459 | 8 | +| 557 | 1 | 452 | 8 | ## Client diff --git a/api_docs/embeddable_enhanced.mdx b/api_docs/embeddable_enhanced.mdx index 299081466baf1..5d4a086ffa67c 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-02-05 +date: 2024-02-07 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 fbcf6a75bfb13..d6b88067724a5 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-02-05 +date: 2024-02-07 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 217e17339ebec..976efc6f352f0 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'enterpriseSearch'] --- import enterpriseSearchObj from './enterprise_search.devdocs.json'; diff --git a/api_docs/es_ui_shared.mdx b/api_docs/es_ui_shared.mdx index 5d33f3b14500c..068973c413332 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'esUiShared'] --- import esUiSharedObj from './es_ui_shared.devdocs.json'; diff --git a/api_docs/event_annotation.devdocs.json b/api_docs/event_annotation.devdocs.json index 1f5334176836c..20bf6e85161a0 100644 --- a/api_docs/event_annotation.devdocs.json +++ b/api_docs/event_annotation.devdocs.json @@ -1085,7 +1085,7 @@ "signature": [ "{ item: ", "EventAnnotationGroupSavedObject", - "; }" + "; meta?: undefined; }" ], "path": "src/plugins/event_annotation/common/content_management/v1/types.ts", "deprecated": false, diff --git a/api_docs/event_annotation.mdx b/api_docs/event_annotation.mdx index a16f3952d09e0..04ff788135c3b 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-02-05 +date: 2024-02-07 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 c5bd40e00d726..678653f01675d 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-02-05 +date: 2024-02-07 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 d7f29e4b164d6..68c65eed115f8 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventLog'] --- import eventLogObj from './event_log.devdocs.json'; diff --git a/api_docs/exploratory_view.mdx b/api_docs/exploratory_view.mdx index 61c99e37b20e0..d848fea9dff05 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-02-05 +date: 2024-02-07 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 c6ef2122ec5c1..5a4ab66af2adb 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionError'] --- import expressionErrorObj from './expression_error.devdocs.json'; diff --git a/api_docs/expression_gauge.mdx b/api_docs/expression_gauge.mdx index 4e46045ce1520..8e09a01529a53 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-02-05 +date: 2024-02-07 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 a13d2543b2110..34a2a193157b5 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-02-05 +date: 2024-02-07 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 b6e8121cf6aee..252a9498bf27f 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-02-05 +date: 2024-02-07 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 642417ae2835c..dfd42386bd97f 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-02-05 +date: 2024-02-07 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 71df865ec4b8b..6b382f9dcd5de 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-02-05 +date: 2024-02-07 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 5ee85b3aa9712..0c0dac7590506 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-02-05 +date: 2024-02-07 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 aab0f469e3354..8bf65ad0ab67b 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-02-05 +date: 2024-02-07 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 20d11b3de317e..e3f854290b85c 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-02-05 +date: 2024-02-07 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 ca8e420fd8f7a..e1ad44d967385 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-02-05 +date: 2024-02-07 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 1ae246b6414e1..8c03e3179e3c0 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-02-05 +date: 2024-02-07 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 1cf45ec55dc8c..f6363210818fb 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-02-05 +date: 2024-02-07 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 12a2ba1ad06c1..9872e1d9d71aa 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionXY'] --- import expressionXYObj from './expression_x_y.devdocs.json'; diff --git a/api_docs/expressions.devdocs.json b/api_docs/expressions.devdocs.json index 0ea45e489f34c..410a91f45338d 100644 --- a/api_docs/expressions.devdocs.json +++ b/api_docs/expressions.devdocs.json @@ -6829,7 +6829,7 @@ "label": "useExpressionRenderer", "description": [], "signature": [ - "(nodeRef: React.RefObject, {\n debounce,\n expression,\n hasCustomErrorRenderer,\n onData$,\n onEvent,\n onRender$,\n reload$,\n ...loaderParams\n }: ", + "(nodeRef: React.RefObject, {\n debounce,\n expression,\n hasCustomErrorRenderer,\n onData$,\n onEvent,\n onRender$,\n reload$,\n abortController,\n ...loaderParams\n }: ", { "pluginId": "expressions", "scope": "public", @@ -6863,7 +6863,7 @@ "id": "def-public.useExpressionRenderer.$2", "type": "Object", "tags": [], - "label": "{\n debounce,\n expression,\n hasCustomErrorRenderer,\n onData$,\n onEvent,\n onRender$,\n reload$,\n ...loaderParams\n }", + "label": "{\n debounce,\n expression,\n hasCustomErrorRenderer,\n onData$,\n onEvent,\n onRender$,\n reload$,\n abortController,\n ...loaderParams\n }", "description": [], "signature": [ { @@ -9593,6 +9593,20 @@ "path": "src/plugins/expressions/public/react_expression_renderer/use_expression_renderer.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "expressions", + "id": "def-public.ExpressionRendererParams.abortController", + "type": "Object", + "tags": [], + "label": "abortController", + "description": [], + "signature": [ + "AbortController | undefined" + ], + "path": "src/plugins/expressions/public/react_expression_renderer/use_expression_renderer.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -10984,6 +10998,20 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "expressions", + "id": "def-public.IExpressionLoaderParams.abortController", + "type": "Object", + "tags": [], + "label": "abortController", + "description": [], + "signature": [ + "AbortController | undefined" + ], + "path": "src/plugins/expressions/public/types/index.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "expressions", "id": "def-public.IExpressionLoaderParams.partial", diff --git a/api_docs/expressions.mdx b/api_docs/expressions.mdx index 803cddeac518d..b3e3fa39995d3 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressions'] --- import expressionsObj from './expressions.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/k | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 2222 | 17 | 1760 | 5 | +| 2224 | 17 | 1762 | 5 | ## Client diff --git a/api_docs/features.mdx b/api_docs/features.mdx index 7ba98f964ef01..3d8ee32fce106 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-02-05 +date: 2024-02-07 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 55a5194e37a5b..6cf7b1fccd3d7 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fieldFormats'] --- import fieldFormatsObj from './field_formats.devdocs.json'; diff --git a/api_docs/file_upload.mdx b/api_docs/file_upload.mdx index 9c09f6008b079..a50d4e8e4735e 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fileUpload'] --- import fileUploadObj from './file_upload.devdocs.json'; diff --git a/api_docs/files.mdx b/api_docs/files.mdx index 95c6e49f7f66d..6bdc433a462bf 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-02-05 +date: 2024-02-07 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 9fc51056a7160..bab8111ea9acd 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-02-05 +date: 2024-02-07 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 cd548cf1b7362..de83a55e605a0 100644 --- a/api_docs/fleet.devdocs.json +++ b/api_docs/fleet.devdocs.json @@ -7065,6 +7065,49 @@ } ], "returnComment": [] + }, + { + "parentPluginId": "fleet", + "id": "def-server.ArtifactsClientInterface.fetchAll", + "type": "Function", + "tags": [], + "label": "fetchAll", + "description": [], + "signature": [ + "(options?: ", + "FetchAllArtifactsOptions", + " | undefined) => AsyncIterable<", + { + "pluginId": "fleet", + "scope": "server", + "docId": "kibFleetPluginApi", + "section": "def-server.Artifact", + "text": "Artifact" + }, + "[]>" + ], + "path": "x-pack/plugins/fleet/server/services/artifacts/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "fleet", + "id": "def-server.ArtifactsClientInterface.fetchAll.$1", + "type": "CompoundType", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "FetchAllArtifactsOptions", + " | undefined" + ], + "path": "x-pack/plugins/fleet/server/services/artifacts/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [] } ], "initialIsOpen": false @@ -11299,6 +11342,146 @@ } ], "returnComment": [] + }, + { + "parentPluginId": "fleet", + "id": "def-server.PackagePolicyClient.fetchAllItemIds", + "type": "Function", + "tags": [], + "label": "fetchAllItemIds", + "description": [ + "\nReturns an `AsyncIterable` for retrieving all integration policy IDs" + ], + "signature": [ + "(soClient: ", + { + "pluginId": "@kbn/core-saved-objects-api-server", + "scope": "common", + "docId": "kibKbnCoreSavedObjectsApiServerPluginApi", + "section": "def-common.SavedObjectsClientContract", + "text": "SavedObjectsClientContract" + }, + ", options?: ", + "PackagePolicyClientFetchAllItemIdsOptions", + " | undefined) => AsyncIterable" + ], + "path": "x-pack/plugins/fleet/server/services/package_policy_service.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "fleet", + "id": "def-server.PackagePolicyClient.fetchAllItemIds.$1", + "type": "Object", + "tags": [], + "label": "soClient", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-saved-objects-api-server", + "scope": "common", + "docId": "kibKbnCoreSavedObjectsApiServerPluginApi", + "section": "def-common.SavedObjectsClientContract", + "text": "SavedObjectsClientContract" + } + ], + "path": "x-pack/plugins/fleet/server/services/package_policy_service.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "fleet", + "id": "def-server.PackagePolicyClient.fetchAllItemIds.$2", + "type": "Object", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "PackagePolicyClientFetchAllItemIdsOptions", + " | undefined" + ], + "path": "x-pack/plugins/fleet/server/services/package_policy_service.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [] + }, + { + "parentPluginId": "fleet", + "id": "def-server.PackagePolicyClient.fetchAllItems", + "type": "Function", + "tags": [], + "label": "fetchAllItems", + "description": [ + "\nReturns an `AsyncIterable` for retrieving all integration policies" + ], + "signature": [ + "(soClient: ", + { + "pluginId": "@kbn/core-saved-objects-api-server", + "scope": "common", + "docId": "kibKbnCoreSavedObjectsApiServerPluginApi", + "section": "def-common.SavedObjectsClientContract", + "text": "SavedObjectsClientContract" + }, + ", options?: ", + "PackagePolicyClientFetchAllItemsOptions", + " | undefined) => AsyncIterable<", + { + "pluginId": "fleet", + "scope": "common", + "docId": "kibFleetPluginApi", + "section": "def-common.PackagePolicy", + "text": "PackagePolicy" + }, + "[]>" + ], + "path": "x-pack/plugins/fleet/server/services/package_policy_service.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "fleet", + "id": "def-server.PackagePolicyClient.fetchAllItems.$1", + "type": "Object", + "tags": [], + "label": "soClient", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-saved-objects-api-server", + "scope": "common", + "docId": "kibKbnCoreSavedObjectsApiServerPluginApi", + "section": "def-common.SavedObjectsClientContract", + "text": "SavedObjectsClientContract" + } + ], + "path": "x-pack/plugins/fleet/server/services/package_policy_service.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "fleet", + "id": "def-server.PackagePolicyClient.fetchAllItems.$2", + "type": "Object", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "PackagePolicyClientFetchAllItemsOptions", + " | undefined" + ], + "path": "x-pack/plugins/fleet/server/services/package_policy_service.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [] } ], "initialIsOpen": false diff --git a/api_docs/fleet.mdx b/api_docs/fleet.mdx index 3e24971745fb4..e457af73948af 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-02-05 +date: 2024-02-07 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 | |-------------------|-----------|------------------------|-----------------| -| 1221 | 3 | 1104 | 51 | +| 1229 | 3 | 1110 | 54 | ## Client diff --git a/api_docs/global_search.mdx b/api_docs/global_search.mdx index 63b3f7f82a14b..031e986467b92 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-02-05 +date: 2024-02-07 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 087f4670f228e..c68994c5fdf71 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-02-05 +date: 2024-02-07 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 ffb69b5f08e87..f004aff979b57 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-02-05 +date: 2024-02-07 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 43a1ba9138d7a..561f830f5ed1a 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-02-05 +date: 2024-02-07 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 c1d559b6f98eb..f6cf3f300f557 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'indexLifecycleManagement'] --- import indexLifecycleManagementObj from './index_lifecycle_management.devdocs.json'; diff --git a/api_docs/index_management.devdocs.json b/api_docs/index_management.devdocs.json index 54c259862cbf5..930f9a12fe45b 100644 --- a/api_docs/index_management.devdocs.json +++ b/api_docs/index_management.devdocs.json @@ -829,6 +829,20 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "indexManagement", + "id": "def-server.LegacyTemplateSerialized.deprecated", + "type": "CompoundType", + "tags": [], + "label": "deprecated", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "x-pack/plugins/index_management/common/types/templates.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "indexManagement", "id": "def-server.LegacyTemplateSerialized.mappings", @@ -1138,6 +1152,20 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "indexManagement", + "id": "def-common.ComponentTemplateDeserialized.isDeprecated", + "type": "CompoundType", + "tags": [], + "label": "isDeprecated", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "x-pack/plugins/index_management/common/types/component_templates.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "indexManagement", "id": "def-common.ComponentTemplateDeserialized._kbnMeta", @@ -1279,6 +1307,20 @@ "path": "x-pack/plugins/index_management/common/types/component_templates.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "indexManagement", + "id": "def-common.ComponentTemplateListItem.isDeprecated", + "type": "CompoundType", + "tags": [], + "label": "isDeprecated", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "x-pack/plugins/index_management/common/types/component_templates.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -1348,6 +1390,20 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "indexManagement", + "id": "def-common.ComponentTemplateSerialized.deprecated", + "type": "CompoundType", + "tags": [], + "label": "deprecated", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "x-pack/plugins/index_management/common/types/component_templates.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "indexManagement", "id": "def-common.ComponentTemplateSerialized._meta", @@ -2427,6 +2483,20 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "indexManagement", + "id": "def-common.LegacyTemplateSerialized.deprecated", + "type": "CompoundType", + "tags": [], + "label": "deprecated", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "x-pack/plugins/index_management/common/types/templates.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "indexManagement", "id": "def-common.LegacyTemplateSerialized.mappings", @@ -2766,6 +2836,20 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "indexManagement", + "id": "def-common.TemplateDeserialized.deprecated", + "type": "CompoundType", + "tags": [], + "label": "deprecated", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "x-pack/plugins/index_management/common/types/templates.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "indexManagement", "id": "def-common.TemplateDeserialized._meta", @@ -2977,6 +3061,20 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "indexManagement", + "id": "def-common.TemplateListItem.deprecated", + "type": "CompoundType", + "tags": [], + "label": "deprecated", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "x-pack/plugins/index_management/common/types/templates.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "indexManagement", "id": "def-common.TemplateListItem.ilmPolicy", @@ -3083,6 +3181,20 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "indexManagement", + "id": "def-common.TemplateSerialized.deprecated", + "type": "CompoundType", + "tags": [], + "label": "deprecated", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "x-pack/plugins/index_management/common/types/templates.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "indexManagement", "id": "def-common.TemplateSerialized.composed_of", diff --git a/api_docs/index_management.mdx b/api_docs/index_management.mdx index 31cc7cab04a6d..3f8c2ff001f18 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'indexManagement'] --- import indexManagementObj from './index_management.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/platform-deployment-management](https://github.com/orgs/elasti | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 215 | 0 | 210 | 4 | +| 223 | 0 | 218 | 4 | ## Client diff --git a/api_docs/infra.mdx b/api_docs/infra.mdx index 72fc5909eeb7f..302e477335d1c 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-02-05 +date: 2024-02-07 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 71f0fb2b177cd..cf427bd90c527 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-02-05 +date: 2024-02-07 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 d174ae3ddb389..cdeab5a224d81 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'inspector'] --- import inspectorObj from './inspector.devdocs.json'; diff --git a/api_docs/interactive_setup.mdx b/api_docs/interactive_setup.mdx index 0a28b1590a062..9b1884dc2a44b 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'interactiveSetup'] --- import interactiveSetupObj from './interactive_setup.devdocs.json'; diff --git a/api_docs/kbn_ace.mdx b/api_docs/kbn_ace.mdx index d5cca622437af..8caec772965ee 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-02-05 +date: 2024-02-07 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 e8266c0a45775..88a29fe0597bd 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-02-05 +date: 2024-02-07 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 4ef79e9c9675b..3258abdd89ffc 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-components'] --- import kbnAiopsComponentsObj from './kbn_aiops_components.devdocs.json'; diff --git a/api_docs/kbn_aiops_utils.mdx b/api_docs/kbn_aiops_utils.mdx index 59145cb86b901..93defed009bb1 100644 --- a/api_docs/kbn_aiops_utils.mdx +++ b/api_docs/kbn_aiops_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-aiops-utils title: "@kbn/aiops-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/aiops-utils plugin -date: 2024-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-utils'] --- import kbnAiopsUtilsObj from './kbn_aiops_utils.devdocs.json'; diff --git a/api_docs/kbn_alerting_api_integration_helpers.mdx b/api_docs/kbn_alerting_api_integration_helpers.mdx index 1400e923b1227..9a697b4d61449 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerting-api-integration-helpers'] --- import kbnAlertingApiIntegrationHelpersObj from './kbn_alerting_api_integration_helpers.devdocs.json'; diff --git a/api_docs/kbn_alerting_state_types.devdocs.json b/api_docs/kbn_alerting_state_types.devdocs.json index 344736f793222..4beda471f14be 100644 --- a/api_docs/kbn_alerting_state_types.devdocs.json +++ b/api_docs/kbn_alerting_state_types.devdocs.json @@ -51,6 +51,8 @@ "BooleanC", "; pendingRecoveredCount: ", "NumberC", + "; activeCount: ", + "NumberC", "; }>>; trackedAlertsRecovered: ", "RecordC", "<", @@ -71,6 +73,8 @@ "BooleanC", "; pendingRecoveredCount: ", "NumberC", + "; activeCount: ", + "NumberC", "; }>>; }>" ], "path": "x-pack/packages/kbn-alerting-state-types/src/lifecycle_state.ts", @@ -255,7 +259,7 @@ "label": "TrackedLifecycleAlertState", "description": [], "signature": [ - "{ alertId: string; alertUuid: string; started: string; flappingHistory: boolean[]; flapping: boolean; pendingRecoveredCount: number; }" + "{ alertId: string; alertUuid: string; started: string; flappingHistory: boolean[]; flapping: boolean; pendingRecoveredCount: number; activeCount: number; }" ], "path": "x-pack/packages/kbn-alerting-state-types/src/lifecycle_state.ts", "deprecated": false, @@ -272,7 +276,7 @@ "\nThis is redefined instead of derived from above `wrappedStateRt` because\nthere's no easy way to instantiate generic values such as the runtime type\nfactory function." ], "signature": [ - "RuleTypeState & { wrapped: State; trackedAlerts: Record; trackedAlertsRecovered: Record; }" + "RuleTypeState & { wrapped: State; trackedAlerts: Record; trackedAlertsRecovered: Record; }" ], "path": "x-pack/packages/kbn-alerting-state-types/src/lifecycle_state.ts", "deprecated": false, diff --git a/api_docs/kbn_alerting_state_types.mdx b/api_docs/kbn_alerting_state_types.mdx index 78d689d8819bb..d0fee964360aa 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-02-05 +date: 2024-02-07 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 aa5b0923693eb..2071cb1dd96e4 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-02-05 +date: 2024-02-07 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 53979fa0093fb..2fdfdd3238dad 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerts-as-data-utils'] --- import kbnAlertsAsDataUtilsObj from './kbn_alerts_as_data_utils.devdocs.json'; diff --git a/api_docs/kbn_alerts_ui_shared.mdx b/api_docs/kbn_alerts_ui_shared.mdx index 67b9fbffb72e2..d7b8009e7e092 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-02-05 +date: 2024-02-07 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 c09e2d6791be3..fd4d432cbe617 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics'] --- import kbnAnalyticsObj from './kbn_analytics.devdocs.json'; diff --git a/api_docs/kbn_analytics_client.mdx b/api_docs/kbn_analytics_client.mdx index 3cd80ec061662..99db6e29b00f1 100644 --- a/api_docs/kbn_analytics_client.mdx +++ b/api_docs/kbn_analytics_client.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-client title: "@kbn/analytics-client" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-client plugin -date: 2024-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-client'] --- import kbnAnalyticsClientObj from './kbn_analytics_client.devdocs.json'; diff --git a/api_docs/kbn_analytics_collection_utils.mdx b/api_docs/kbn_analytics_collection_utils.mdx index 26bcd8cf3185e..f18fd4790af55 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-collection-utils'] --- import kbnAnalyticsCollectionUtilsObj from './kbn_analytics_collection_utils.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx index 4f964b5744bc2..bbaf361280b49 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-browser title: "@kbn/analytics-shippers-elastic-v3-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-elastic-v3-browser plugin -date: 2024-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-browser'] --- import kbnAnalyticsShippersElasticV3BrowserObj from './kbn_analytics_shippers_elastic_v3_browser.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx index 5d9128b1f5b99..613455dad34ae 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-common title: "@kbn/analytics-shippers-elastic-v3-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-elastic-v3-common plugin -date: 2024-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-common'] --- import kbnAnalyticsShippersElasticV3CommonObj from './kbn_analytics_shippers_elastic_v3_common.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx index edaed04457277..8db8c8d132255 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-server title: "@kbn/analytics-shippers-elastic-v3-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-elastic-v3-server plugin -date: 2024-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-server'] --- import kbnAnalyticsShippersElasticV3ServerObj from './kbn_analytics_shippers_elastic_v3_server.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_fullstory.mdx b/api_docs/kbn_analytics_shippers_fullstory.mdx index 4e5f9fd551977..964b428217d0e 100644 --- a/api_docs/kbn_analytics_shippers_fullstory.mdx +++ b/api_docs/kbn_analytics_shippers_fullstory.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-fullstory title: "@kbn/analytics-shippers-fullstory" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-fullstory plugin -date: 2024-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-fullstory'] --- import kbnAnalyticsShippersFullstoryObj from './kbn_analytics_shippers_fullstory.devdocs.json'; diff --git a/api_docs/kbn_apm_config_loader.mdx b/api_docs/kbn_apm_config_loader.mdx index e918746be1ca8..00d58ccb6a74c 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-config-loader'] --- import kbnApmConfigLoaderObj from './kbn_apm_config_loader.devdocs.json'; diff --git a/api_docs/kbn_apm_synthtrace.mdx b/api_docs/kbn_apm_synthtrace.mdx index b5870c26be1e0..db071f9e01471 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-02-05 +date: 2024-02-07 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 42cff415373d5..6602bb36b7029 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-synthtrace-client'] --- import kbnApmSynthtraceClientObj from './kbn_apm_synthtrace_client.devdocs.json'; diff --git a/api_docs/kbn_apm_utils.mdx b/api_docs/kbn_apm_utils.mdx index 6fe9328e1cf07..14eceaf9f6c6e 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-utils'] --- import kbnApmUtilsObj from './kbn_apm_utils.devdocs.json'; diff --git a/api_docs/kbn_axe_config.mdx b/api_docs/kbn_axe_config.mdx index 5418c9ed0d283..8a2a7331cbbed 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-02-05 +date: 2024-02-07 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 63ff33fd37043..e0f13dacf12e2 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-02-05 +date: 2024-02-07 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 0f78b04ee28d8..e88e9cf0f46af 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-02-05 +date: 2024-02-07 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 5984a997bb2ff..37bb79a8bfabb 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-02-05 +date: 2024-02-07 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 5baa86c9f2089..595ae33bf58d4 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-02-05 +date: 2024-02-07 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 8e9905b1c45de..8267f3c16f1d7 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-02-05 +date: 2024-02-07 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 e08c801769240..375e367a2ea51 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-02-05 +date: 2024-02-07 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 0eeadcc2cc137..9c904d7307dc2 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-02-05 +date: 2024-02-07 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 cb5c58847d706..1594dae51b9c8 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-02-05 +date: 2024-02-07 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 8619e193c5ba5..5e23c1379c324 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-02-05 +date: 2024-02-07 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 e3345a36fe93a..d274a27de597c 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-02-05 +date: 2024-02-07 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 d8cd1780e5bab..e245c1d86e968 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cli-dev-mode'] --- import kbnCliDevModeObj from './kbn_cli_dev_mode.devdocs.json'; diff --git a/api_docs/kbn_code_editor.devdocs.json b/api_docs/kbn_code_editor.devdocs.json index 876090f16122a..c523a040f5128 100644 --- a/api_docs/kbn_code_editor.devdocs.json +++ b/api_docs/kbn_code_editor.devdocs.json @@ -542,6 +542,22 @@ "path": "packages/shared-ux/code_editor/impl/code_editor.tsx", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "@kbn/code-editor", + "id": "def-common.CodeEditorProps.fitToContent", + "type": "Object", + "tags": [], + "label": "fitToContent", + "description": [ + "\nEnables the editor to grow vertically to fit its content.\nThis option overrides the `height` option." + ], + "signature": [ + "{ minLines?: number | undefined; maxLines?: number | undefined; } | undefined" + ], + "path": "packages/shared-ux/code_editor/impl/code_editor.tsx", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false diff --git a/api_docs/kbn_code_editor.mdx b/api_docs/kbn_code_editor.mdx index 57373afbf3992..3817bba5764d9 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/code-editor'] --- import kbnCodeEditorObj from './kbn_code_editor.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 | |-------------------|-----------|------------------------|-----------------| -| 36 | 0 | 15 | 0 | +| 37 | 0 | 15 | 0 | ## Common diff --git a/api_docs/kbn_code_editor_mock.mdx b/api_docs/kbn_code_editor_mock.mdx index 0f40b430d78b4..42c0c32b12e51 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-02-05 +date: 2024-02-07 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 47bb4034f66da..5ba405fce4dbc 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/code-owners'] --- import kbnCodeOwnersObj from './kbn_code_owners.devdocs.json'; diff --git a/api_docs/kbn_coloring.devdocs.json b/api_docs/kbn_coloring.devdocs.json index b16ebabd965d3..64eb4e83e7721 100644 --- a/api_docs/kbn_coloring.devdocs.json +++ b/api_docs/kbn_coloring.devdocs.json @@ -435,6 +435,39 @@ "returnComment": [], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/coloring", + "id": "def-common.getActivePaletteName", + "type": "Function", + "tags": [], + "label": "getActivePaletteName", + "description": [], + "signature": [ + "(name: string | undefined) => string" + ], + "path": "packages/kbn-coloring/src/palettes/utils.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/coloring", + "id": "def-common.getActivePaletteName.$1", + "type": "string", + "tags": [], + "label": "name", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-coloring/src/palettes/utils.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/coloring", "id": "def-common.getAssignmentColor", @@ -2754,6 +2787,21 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/coloring", + "id": "def-common.COMPLEMENTARY_PALETTE", + "type": "string", + "tags": [], + "label": "COMPLEMENTARY_PALETTE", + "description": [], + "signature": [ + "\"complementary\"" + ], + "path": "packages/kbn-coloring/src/palettes/constants.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/coloring", "id": "def-common.CUSTOM_PALETTE", @@ -2799,6 +2847,21 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/coloring", + "id": "def-common.DEFAULT_FALLBACK_PALETTE", + "type": "string", + "tags": [], + "label": "DEFAULT_FALLBACK_PALETTE", + "description": [], + "signature": [ + "\"default\"" + ], + "path": "packages/kbn-coloring/src/palettes/constants.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/coloring", "id": "def-common.DEFAULT_MAX_STOP", @@ -2919,6 +2982,21 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/coloring", + "id": "def-common.LEGACY_COMPLIMENTARY_PALETTE", + "type": "string", + "tags": [], + "label": "LEGACY_COMPLIMENTARY_PALETTE", + "description": [], + "signature": [ + "\"complimentary\"" + ], + "path": "packages/kbn-coloring/src/palettes/constants.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/coloring", "id": "def-common.NEUTRAL_COLOR_DARK", diff --git a/api_docs/kbn_coloring.mdx b/api_docs/kbn_coloring.mdx index 11b1ec5653f50..89c8701f1916e 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/coloring'] --- import kbnColoringObj from './kbn_coloring.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 | |-------------------|-----------|------------------------|-----------------| -| 206 | 0 | 169 | 8 | +| 211 | 0 | 174 | 8 | ## Common diff --git a/api_docs/kbn_config.mdx b/api_docs/kbn_config.mdx index 94fffbef2428f..673574632e619 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config'] --- import kbnConfigObj from './kbn_config.devdocs.json'; diff --git a/api_docs/kbn_config_mocks.mdx b/api_docs/kbn_config_mocks.mdx index c6f09d76ac318..c35abb86a9578 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-02-05 +date: 2024-02-07 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 fcba42083c390..6ffdca2e390bb 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-02-05 +date: 2024-02-07 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 58c38d680ae32..f1cef23fdb330 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-02-05 +date: 2024-02-07 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 8b3a133c415d3..e26f8646dc4f8 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-02-05 +date: 2024-02-07 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 d184fe171f963..b257e36ca7c58 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-02-05 +date: 2024-02-07 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 6a9fff2b01fd2..4300132ed29f1 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-02-05 +date: 2024-02-07 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 aed45d05e0b4b..9df2310b6aece 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-table-list-view-table'] --- import kbnContentManagementTableListViewTableObj from './kbn_content_management_table_list_view_table.devdocs.json'; diff --git a/api_docs/kbn_content_management_utils.devdocs.json b/api_docs/kbn_content_management_utils.devdocs.json index df657b5cb788b..1441b673b2afc 100644 --- a/api_docs/kbn_content_management_utils.devdocs.json +++ b/api_docs/kbn_content_management_utils.devdocs.json @@ -1329,7 +1329,7 @@ "section": "def-common.SOWithMetadata", "text": "SOWithMetadata" }, - "; }" + "; meta?: undefined; }" ], "path": "packages/kbn-content-management-utils/src/types.ts", "deprecated": false, @@ -1455,7 +1455,7 @@ "section": "def-common.SOWithMetadataPartial", "text": "SOWithMetadataPartial" }, - "; }" + "; meta?: undefined; }" ], "path": "packages/kbn-content-management-utils/src/types.ts", "deprecated": false, @@ -1756,7 +1756,7 @@ "section": "def-common.SOWithMetadata", "text": "SOWithMetadata" }, - "; }" + "; meta?: undefined; }" ], "path": "packages/kbn-content-management-utils/src/types.ts", "deprecated": false, @@ -1850,7 +1850,7 @@ "section": "def-common.SOWithMetadataPartial", "text": "SOWithMetadataPartial" }, - "; }" + "; meta?: undefined; }" ], "path": "packages/kbn-content-management-utils/src/types.ts", "deprecated": false, diff --git a/api_docs/kbn_content_management_utils.mdx b/api_docs/kbn_content_management_utils.mdx index 99e2a5e7430fb..6e5578f734936 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-utils'] --- import kbnContentManagementUtilsObj from './kbn_content_management_utils.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser.mdx b/api_docs/kbn_core_analytics_browser.mdx index 085eeba21233c..c0ea027c1c7a3 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser'] --- import kbnCoreAnalyticsBrowserObj from './kbn_core_analytics_browser.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser_internal.mdx b/api_docs/kbn_core_analytics_browser_internal.mdx index 7c3ff42ee4764..2cf554b3d1091 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-02-05 +date: 2024-02-07 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 48fdf00292192..33672c6af029f 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser-mocks'] --- import kbnCoreAnalyticsBrowserMocksObj from './kbn_core_analytics_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server.mdx b/api_docs/kbn_core_analytics_server.mdx index a27df9d782808..164e19682cf6e 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server'] --- import kbnCoreAnalyticsServerObj from './kbn_core_analytics_server.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server_internal.mdx b/api_docs/kbn_core_analytics_server_internal.mdx index 99a9281c00401..e85fd93334a5f 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-02-05 +date: 2024-02-07 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 0b8f7f8845333..0f6fe0dca69f7 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-02-05 +date: 2024-02-07 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 13226c267f350..defdbfcf752d9 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-02-05 +date: 2024-02-07 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 240fca1cc8902..e462b53bbe0ae 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-02-05 +date: 2024-02-07 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 637abcad8169f..554783ac18a77 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-02-05 +date: 2024-02-07 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 6c4d4049bbd0c..b9a1be9ff7baf 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-common'] --- import kbnCoreApplicationCommonObj from './kbn_core_application_common.devdocs.json'; diff --git a/api_docs/kbn_core_apps_browser_internal.mdx b/api_docs/kbn_core_apps_browser_internal.mdx index 669facc03e0df..2cf86d87ee446 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-02-05 +date: 2024-02-07 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 4b6c9261c339a..c342f014d3371 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-02-05 +date: 2024-02-07 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 70608e957e622..0f458ac648a85 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-02-05 +date: 2024-02-07 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 1803b3d35cd18..fb3409a1b72ae 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-02-05 +date: 2024-02-07 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 0d26530acef13..121a253b7fd34 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-02-05 +date: 2024-02-07 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 b72ce6afa066e..29d44218fba40 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-02-05 +date: 2024-02-07 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 fa7d37327bd2e..e7d08297f3f01 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-02-05 +date: 2024-02-07 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 f420dc4af922e..5bd6809ec7fc5 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-02-05 +date: 2024-02-07 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 6df201c482ea0..9479895594b92 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-02-05 +date: 2024-02-07 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 1cf9f45f60b69..503f1dd752f27 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-02-05 +date: 2024-02-07 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 ce2fe344b79e9..40b5ec41d5b07 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-server-mocks'] --- import kbnCoreCapabilitiesServerMocksObj from './kbn_core_capabilities_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_chrome_browser.mdx b/api_docs/kbn_core_chrome_browser.mdx index 21c4a202f3ecf..5c0687ba846e3 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-02-05 +date: 2024-02-07 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 d1db64ae3eeb9..175d427be1336 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-02-05 +date: 2024-02-07 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 643ca5297f12c..9c964617deece 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-02-05 +date: 2024-02-07 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 20c78e358148c..31a20ae4eb4a8 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-02-05 +date: 2024-02-07 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 709e6ffcc405c..b58dcee47c56c 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-02-05 +date: 2024-02-07 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 febfdbce68ee7..3cc6dc60b7047 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-02-05 +date: 2024-02-07 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 213ce432ee355..9143f421c163f 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-02-05 +date: 2024-02-07 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 1f1d9f56ac4b5..2255d80da4822 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-02-05 +date: 2024-02-07 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 fcce2aac0cd14..54bb7f13f99b4 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-02-05 +date: 2024-02-07 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 2f842c86b8d15..edaac85c0d9ed 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-02-05 +date: 2024-02-07 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 c9b4d50c9c184..ae7f085a73371 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-02-05 +date: 2024-02-07 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 b865e2ed42fe3..fe2bc41f4598a 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-02-05 +date: 2024-02-07 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 d9200245f0e24..ca9cf933ee7b0 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-02-05 +date: 2024-02-07 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 8eafd0bb2a621..063ad28cfb84f 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-02-05 +date: 2024-02-07 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 9b2e9efece93b..721614015137b 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-02-05 +date: 2024-02-07 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 c58ea72e99d53..3c4f18032a02d 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-02-05 +date: 2024-02-07 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 b4498f2c5b803..c313423616554 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-02-05 +date: 2024-02-07 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 5da74c02ad61f..1665c11768fb1 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-02-05 +date: 2024-02-07 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 e381656d84658..5f563aad21268 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-02-05 +date: 2024-02-07 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 0ab40b56a69c8..aa05ee1e48aec 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-02-05 +date: 2024-02-07 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 0ee4592050831..4a47db4503d26 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-02-05 +date: 2024-02-07 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 1bfdbfb770d34..a535c44c1086a 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-02-05 +date: 2024-02-07 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 11af9db0eac13..82ddfe913f043 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-02-05 +date: 2024-02-07 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 a6b51fdedbbe8..063206d392085 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-02-05 +date: 2024-02-07 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 ae201fca94325..2951f1f0ae58e 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-02-05 +date: 2024-02-07 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 495af90e7bd6d..62ca17a0e5ed4 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-02-05 +date: 2024-02-07 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 503015fe01917..8b13127936232 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-02-05 +date: 2024-02-07 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 3d303aee72367..dd8d00c19a9ca 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-02-05 +date: 2024-02-07 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 cb6e6d62bb424..f27c008baf813 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-02-05 +date: 2024-02-07 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 8ca335a6fdec3..c269780d72904 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-02-05 +date: 2024-02-07 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 bee1f05b344d1..090d6a37c599a 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-02-05 +date: 2024-02-07 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 63b81da9af181..b9f02f7b80635 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-02-05 +date: 2024-02-07 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 1d5b562f4305b..72c15222edf16 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-02-05 +date: 2024-02-07 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 ef26f0992151f..7084245ddd0c6 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-02-05 +date: 2024-02-07 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 07804abbd9895..7079ebd6a73a8 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-02-05 +date: 2024-02-07 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 8dfd42d325bd8..b22d1453c6f5b 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-02-05 +date: 2024-02-07 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 5a58f11523924..182e61b01ac29 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-02-05 +date: 2024-02-07 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 00a53e7f2f3c0..c5611f0d5ecbb 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-02-05 +date: 2024-02-07 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 399d22d78241f..ec1a657fa1338 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-02-05 +date: 2024-02-07 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 9ce158d8de388..b60018e61198c 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-02-05 +date: 2024-02-07 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 e88b5c428fdfe..efdf299352432 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-02-05 +date: 2024-02-07 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 048a8e1aada1e..22864c7ec1dbc 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-02-05 +date: 2024-02-07 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 463245b05197f..6559762b2f80b 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-02-05 +date: 2024-02-07 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 7f9ae9e9f238d..6a4a7db0eaed2 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-02-05 +date: 2024-02-07 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 cccbe783e8924..9304825ba54e3 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-02-05 +date: 2024-02-07 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 89fa3f368d3ab..2cf5108285fca 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-02-05 +date: 2024-02-07 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 e70790787af07..c41cdefa3da7f 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-02-05 +date: 2024-02-07 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 caf95c21400b8..9124cb698c653 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-02-05 +date: 2024-02-07 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.mdx b/api_docs/kbn_core_http_server.mdx index fa1e35c7da872..de2bc56895147 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-02-05 +date: 2024-02-07 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 dfcdaf3181626..80085f910f5ee 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-02-05 +date: 2024-02-07 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 570b81aa1004a..7a2b9c8ac1125 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-02-05 +date: 2024-02-07 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 d6aa4262b7c1d..d9c5d66a99bb1 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-02-05 +date: 2024-02-07 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 c66ca8f93bd5a..30c4af84e5a4f 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-02-05 +date: 2024-02-07 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 48a3a858e082b..ba4733defd342 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-02-05 +date: 2024-02-07 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 c1f320a85ef9f..4a15dc47c8167 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-02-05 +date: 2024-02-07 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 b8acd7e28a07b..6cf979f0261fa 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-02-05 +date: 2024-02-07 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 30fadd80ee2c2..4c587e63db3b5 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-02-05 +date: 2024-02-07 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 ae167452b9c4b..ab2a4d0be730b 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-02-05 +date: 2024-02-07 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 859fde44f659e..806b69e27e05c 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-integrations-browser-mocks'] --- import kbnCoreIntegrationsBrowserMocksObj from './kbn_core_integrations_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_browser.mdx b/api_docs/kbn_core_lifecycle_browser.mdx index a11d89ffdf525..68a98c7212262 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-02-05 +date: 2024-02-07 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 af87e39c4fb54..748950c569ab0 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-browser-mocks'] --- import kbnCoreLifecycleBrowserMocksObj from './kbn_core_lifecycle_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_server.mdx b/api_docs/kbn_core_lifecycle_server.mdx index 7acdf5d6b6773..151e4ffa39980 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-02-05 +date: 2024-02-07 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 85d8b09eaecf1..d64103a09a6cf 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-02-05 +date: 2024-02-07 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 e71e1227de8e5..5a382a3192a55 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-02-05 +date: 2024-02-07 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 329f56a5b98f2..705c34f87bab2 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-02-05 +date: 2024-02-07 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 f450aed270455..4742b413aa6df 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-02-05 +date: 2024-02-07 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 2a382a6869a5d..ca1e370734c6c 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-02-05 +date: 2024-02-07 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 b5f5923dfdc69..85d427df7c711 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-02-05 +date: 2024-02-07 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 69e2f5ae9221d..0a4f67b9a7539 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-02-05 +date: 2024-02-07 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 96311aa2a14f9..df82765ea644e 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-02-05 +date: 2024-02-07 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 bc57b527f09ce..d73f84ec7fd4e 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-02-05 +date: 2024-02-07 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 021b70c157241..e1e27f1cdf290 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-02-05 +date: 2024-02-07 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 5f67b143886eb..7eac5a6600a8b 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-02-05 +date: 2024-02-07 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 a43ba54309963..7d0b25e8fabd5 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-02-05 +date: 2024-02-07 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 ea3aa6a7ac3b8..4d7d6a2e2d222 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-02-05 +date: 2024-02-07 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 7e6da0c36577f..e7968c98c421a 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-02-05 +date: 2024-02-07 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 f6ad6b2fd3939..562ac202418cf 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-02-05 +date: 2024-02-07 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 e30615037175d..9f3cdd38409cb 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-02-05 +date: 2024-02-07 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 689f69a72b202..8beea22f363c9 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-02-05 +date: 2024-02-07 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 8f31edf5464cc..0c9fe7d2c3f1c 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-02-05 +date: 2024-02-07 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 dadbd95fc3b3c..3607a652afbc4 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-02-05 +date: 2024-02-07 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 3830a7288c189..eb3bea3079a57 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-02-05 +date: 2024-02-07 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 f3ec8886f06f0..c7ed781fafece 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-02-05 +date: 2024-02-07 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 136881ab4fc04..fbb9d03805dc6 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-02-05 +date: 2024-02-07 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 2c7f9c4e87a7b..93bf10eaa2ea8 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-02-05 +date: 2024-02-07 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 4279237d365ec..d566c128b6962 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-02-05 +date: 2024-02-07 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 f73413770fdab..a6427417916f3 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-02-05 +date: 2024-02-07 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 c02d32dfcbe7d..b8ad18194e5dd 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-02-05 +date: 2024-02-07 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 8dee41696dc88..5263ad7d7c4d4 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-02-05 +date: 2024-02-07 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 471f1c7ca59c8..f2e104f8c13ff 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-02-05 +date: 2024-02-07 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 295750acd22df..88c731a141f5d 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-02-05 +date: 2024-02-07 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 8107b69f7a95e..4e1d813cdd848 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-02-05 +date: 2024-02-07 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 43ae0f465cc6c..7d5928c49700c 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-02-05 +date: 2024-02-07 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 f54bf8199c714..1c53b5d415799 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-02-05 +date: 2024-02-07 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 5802d71e58df6..3323d8bd2395e 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-02-05 +date: 2024-02-07 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 a29819e2f6685..66b77f3667e5b 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-browser'] --- import kbnCoreSavedObjectsApiBrowserObj from './kbn_core_saved_objects_api_browser.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_server.devdocs.json b/api_docs/kbn_core_saved_objects_api_server.devdocs.json index 606ddff1badbf..3b1b515c44655 100644 --- a/api_docs/kbn_core_saved_objects_api_server.devdocs.json +++ b/api_docs/kbn_core_saved_objects_api_server.devdocs.json @@ -2688,7 +2688,7 @@ }, { "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/data/rule/types/rule_attributes.ts" + "path": "x-pack/plugins/alerting/common/rule.ts" }, { "plugin": "alerting", @@ -2708,7 +2708,7 @@ }, { "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/rules_client/common/inject_references.ts" + "path": "x-pack/plugins/alerting/server/data/rule/types/rule_attributes.ts" }, { "plugin": "alerting", @@ -2716,7 +2716,7 @@ }, { "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/types.ts" + "path": "x-pack/plugins/alerting/server/rules_client/common/inject_references.ts" }, { "plugin": "alerting", diff --git a/api_docs/kbn_core_saved_objects_api_server.mdx b/api_docs/kbn_core_saved_objects_api_server.mdx index 0a08b3a9b8dbe..d8043104507f9 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-02-05 +date: 2024-02-07 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 fcfb101ba2166..0d1c875736b9e 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-02-05 +date: 2024-02-07 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 9a857a32e3c46..633b52e220daf 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-02-05 +date: 2024-02-07 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 232c54fbe32a1..f2453ed77fac7 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-02-05 +date: 2024-02-07 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 a21aaeb1e3fd4..ea082bd4f468c 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-02-05 +date: 2024-02-07 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 f53d958c89736..2e5d73970fad8 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-02-05 +date: 2024-02-07 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 1d3c0d9932aa5..d3075162b50f8 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-02-05 +date: 2024-02-07 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 74bd508dcfff6..5c0a58f6b910c 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-02-05 +date: 2024-02-07 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 53b74e285c9b5..be0edf30eb7d9 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-02-05 +date: 2024-02-07 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 9f46580197479..61823b55025d2 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-02-05 +date: 2024-02-07 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 6c07be3c59204..7737c3dfcdc86 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-02-05 +date: 2024-02-07 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 117f4d14d0b38..550d257671bf0 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-migration-server-mocks'] --- import kbnCoreSavedObjectsMigrationServerMocksObj from './kbn_core_saved_objects_migration_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server.devdocs.json b/api_docs/kbn_core_saved_objects_server.devdocs.json index 87524176d7202..9bcb155c4e2bb 100644 --- a/api_docs/kbn_core_saved_objects_server.devdocs.json +++ b/api_docs/kbn_core_saved_objects_server.devdocs.json @@ -6153,7 +6153,7 @@ }, { "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/data/rule/types/rule_attributes.ts" + "path": "x-pack/plugins/alerting/common/rule.ts" }, { "plugin": "alerting", @@ -6173,7 +6173,7 @@ }, { "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/rules_client/common/inject_references.ts" + "path": "x-pack/plugins/alerting/server/data/rule/types/rule_attributes.ts" }, { "plugin": "alerting", @@ -6181,7 +6181,7 @@ }, { "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/types.ts" + "path": "x-pack/plugins/alerting/server/rules_client/common/inject_references.ts" }, { "plugin": "alerting", diff --git a/api_docs/kbn_core_saved_objects_server.mdx b/api_docs/kbn_core_saved_objects_server.mdx index 01f35c9cf4dba..b95826689a525 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-02-05 +date: 2024-02-07 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 96c8759398e3b..3f5db434d2fdc 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-02-05 +date: 2024-02-07 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 069dc11ac83fa..e19262fe28b08 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-02-05 +date: 2024-02-07 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 8c6320483cc0d..90818591895ad 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-utils-server'] --- import kbnCoreSavedObjectsUtilsServerObj from './kbn_core_saved_objects_utils_server.devdocs.json'; diff --git a/api_docs/kbn_core_status_common.mdx b/api_docs/kbn_core_status_common.mdx index d788071b4e79d..c33731667715a 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-02-05 +date: 2024-02-07 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 cf5dbdb9bec10..0f7ee0de49fc1 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-02-05 +date: 2024-02-07 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 f96d99b2712c3..e7e419b29859d 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server'] --- import kbnCoreStatusServerObj from './kbn_core_status_server.devdocs.json'; diff --git a/api_docs/kbn_core_status_server_internal.mdx b/api_docs/kbn_core_status_server_internal.mdx index 1c2687eab4c41..8fdeb27e63cfc 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-02-05 +date: 2024-02-07 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 9222a06006bc3..57d27b65f4fb6 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-02-05 +date: 2024-02-07 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 b067b518bbb78..2f98bdd5ba8de 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-02-05 +date: 2024-02-07 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 448474357be15..41fc0ff7ffbf4 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-02-05 +date: 2024-02-07 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 2a969cdcb9753..ff8569e7830bd 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-02-05 +date: 2024-02-07 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 69043de162426..013ec2fe582f5 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-02-05 +date: 2024-02-07 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 4a93b609ae40b..84474e2d8a5ae 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-02-05 +date: 2024-02-07 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 220a0ff825f7a..a2454558d8774 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-02-05 +date: 2024-02-07 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 54d9ccf2433e9..f27753fd06fe2 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-02-05 +date: 2024-02-07 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 4aa4d401fcc7c..a4fc9aae34f53 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-02-05 +date: 2024-02-07 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 46b1aa5311da1..3602bf5858460 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-02-05 +date: 2024-02-07 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 68f3b406eee22..54aa3ab2c1eb4 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-02-05 +date: 2024-02-07 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 0f2b582737c48..82fcc597ecee1 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-02-05 +date: 2024-02-07 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 6a145fb5186ce..3697a60de41ca 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-02-05 +date: 2024-02-07 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 aafdb5d85b8c4..2087c10bd0b60 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-02-05 +date: 2024-02-07 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 6ca33ad6ff0ee..fa0d22c41b7ea 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-02-05 +date: 2024-02-07 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 479fa03376589..f863f1bd1bc91 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-02-05 +date: 2024-02-07 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 2c4a4980862e4..b34cbe38e1448 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-02-05 +date: 2024-02-07 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 22f27666a8ff0..783abef87e491 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-02-05 +date: 2024-02-07 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 bf014ccd508ba..084adca5b3874 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server-mocks'] --- import kbnCoreUsageDataServerMocksObj from './kbn_core_usage_data_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_user_settings_server.mdx b/api_docs/kbn_core_user_settings_server.mdx index 7d2535d15e6cb..778faf430744d 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-settings-server'] --- import kbnCoreUserSettingsServerObj from './kbn_core_user_settings_server.devdocs.json'; diff --git a/api_docs/kbn_core_user_settings_server_internal.mdx b/api_docs/kbn_core_user_settings_server_internal.mdx index 48ee05a7b6c1f..a5baffb865448 100644 --- a/api_docs/kbn_core_user_settings_server_internal.mdx +++ b/api_docs/kbn_core_user_settings_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-settings-server-internal title: "@kbn/core-user-settings-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-settings-server-internal plugin -date: 2024-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-settings-server-internal'] --- import kbnCoreUserSettingsServerInternalObj from './kbn_core_user_settings_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_user_settings_server_mocks.mdx b/api_docs/kbn_core_user_settings_server_mocks.mdx index c688af99febd5..5e971a65909aa 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-02-05 +date: 2024-02-07 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 332d853096d5b..1e5c50e4a495f 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-02-05 +date: 2024-02-07 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 4dd86d431f3fa..057726904409d 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-02-05 +date: 2024-02-07 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 c824f7d2f2f11..2e0fce4a28662 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-02-05 +date: 2024-02-07 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 9c548b1a60feb..7b8c477df7fb1 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-02-05 +date: 2024-02-07 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 d822f007fc73b..2589f904480fc 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-02-05 +date: 2024-02-07 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 e1bb07cddd133..0bebaa9bbafd7 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-02-05 +date: 2024-02-07 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 f8e79d936ab3c..36519191727ac 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-02-05 +date: 2024-02-07 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 401d540c8bd02..f52ee34042612 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/data-stream-adapter'] --- import kbnDataStreamAdapterObj from './kbn_data_stream_adapter.devdocs.json'; diff --git a/api_docs/kbn_datemath.mdx b/api_docs/kbn_datemath.mdx index 4220ddb3ee54a..3cd087353772c 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/datemath'] --- import kbnDatemathObj from './kbn_datemath.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_analytics.devdocs.json b/api_docs/kbn_deeplinks_analytics.devdocs.json index ad3d96c20ec0c..c5671f4ab84c4 100644 --- a/api_docs/kbn_deeplinks_analytics.devdocs.json +++ b/api_docs/kbn_deeplinks_analytics.devdocs.json @@ -82,6 +82,21 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/deeplinks-analytics", + "id": "def-common.DISCOVER_ESQL_LOCATOR", + "type": "string", + "tags": [], + "label": "DISCOVER_ESQL_LOCATOR", + "description": [], + "signature": [ + "\"DISCOVER_ESQL_LOCATOR\"" + ], + "path": "packages/deeplinks/analytics/constants.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/deeplinks-analytics", "id": "def-common.VISUALIZE_APP_ID", diff --git a/api_docs/kbn_deeplinks_analytics.mdx b/api_docs/kbn_deeplinks_analytics.mdx index fdb92a00c22ae..1c18b0f38da18 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-analytics'] --- import kbnDeeplinksAnalyticsObj from './kbn_deeplinks_analytics.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 | |-------------------|-----------|------------------------|-----------------| -| 5 | 0 | 5 | 0 | +| 6 | 0 | 6 | 0 | ## Common diff --git a/api_docs/kbn_deeplinks_devtools.mdx b/api_docs/kbn_deeplinks_devtools.mdx index 52566968ce5b6..e06313d5e26b1 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-devtools'] --- import kbnDeeplinksDevtoolsObj from './kbn_deeplinks_devtools.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_management.mdx b/api_docs/kbn_deeplinks_management.mdx index 7d10abdbb4533..0110986c4236b 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-02-05 +date: 2024-02-07 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 548d2fc22849a..72559ebe19e04 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-02-05 +date: 2024-02-07 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 5a36771ff4d8b..d98b4ba490885 100644 --- a/api_docs/kbn_deeplinks_observability.devdocs.json +++ b/api_docs/kbn_deeplinks_observability.devdocs.json @@ -22,35 +22,35 @@ "interfaces": [ { "parentPluginId": "@kbn/deeplinks-observability", - "id": "def-common.LogExplorerLocatorParams", + "id": "def-common.LogsExplorerLocatorParams", "type": "Interface", "tags": [], - "label": "LogExplorerLocatorParams", + "label": "LogsExplorerLocatorParams", "description": [], "signature": [ { "pluginId": "@kbn/deeplinks-observability", "scope": "common", "docId": "kibKbnDeeplinksObservabilityPluginApi", - "section": "def-common.LogExplorerLocatorParams", - "text": "LogExplorerLocatorParams" + "section": "def-common.LogsExplorerLocatorParams", + "text": "LogsExplorerLocatorParams" }, " extends ", { "pluginId": "@kbn/deeplinks-observability", "scope": "common", "docId": "kibKbnDeeplinksObservabilityPluginApi", - "section": "def-common.LogExplorerNavigationParams", - "text": "LogExplorerNavigationParams" + "section": "def-common.LogsExplorerNavigationParams", + "text": "LogsExplorerNavigationParams" } ], - "path": "packages/deeplinks/observability/locators/log_explorer.ts", + "path": "packages/deeplinks/observability/locators/logs_explorer.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "@kbn/deeplinks-observability", - "id": "def-common.LogExplorerLocatorParams.dataset", + "id": "def-common.LogsExplorerLocatorParams.dataset", "type": "string", "tags": [], "label": "dataset", @@ -60,7 +60,7 @@ "signature": [ "string | undefined" ], - "path": "packages/deeplinks/observability/locators/log_explorer.ts", + "path": "packages/deeplinks/observability/locators/logs_explorer.ts", "deprecated": false, "trackAdoption": false } @@ -69,18 +69,18 @@ }, { "parentPluginId": "@kbn/deeplinks-observability", - "id": "def-common.LogExplorerNavigationParams", + "id": "def-common.LogsExplorerNavigationParams", "type": "Interface", "tags": [], - "label": "LogExplorerNavigationParams", + "label": "LogsExplorerNavigationParams", "description": [], "signature": [ { "pluginId": "@kbn/deeplinks-observability", "scope": "common", "docId": "kibKbnDeeplinksObservabilityPluginApi", - "section": "def-common.LogExplorerNavigationParams", - "text": "LogExplorerNavigationParams" + "section": "def-common.LogsExplorerNavigationParams", + "text": "LogsExplorerNavigationParams" }, " extends ", { @@ -91,13 +91,13 @@ "text": "SerializableRecord" } ], - "path": "packages/deeplinks/observability/locators/log_explorer.ts", + "path": "packages/deeplinks/observability/locators/logs_explorer.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "@kbn/deeplinks-observability", - "id": "def-common.LogExplorerNavigationParams.timeRange", + "id": "def-common.LogsExplorerNavigationParams.timeRange", "type": "Object", "tags": [], "label": "timeRange", @@ -114,13 +114,13 @@ }, " | undefined" ], - "path": "packages/deeplinks/observability/locators/log_explorer.ts", + "path": "packages/deeplinks/observability/locators/logs_explorer.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "@kbn/deeplinks-observability", - "id": "def-common.LogExplorerNavigationParams.refreshInterval", + "id": "def-common.LogsExplorerNavigationParams.refreshInterval", "type": "Object", "tags": [], "label": "refreshInterval", @@ -137,13 +137,13 @@ }, " | undefined" ], - "path": "packages/deeplinks/observability/locators/log_explorer.ts", + "path": "packages/deeplinks/observability/locators/logs_explorer.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "@kbn/deeplinks-observability", - "id": "def-common.LogExplorerNavigationParams.query", + "id": "def-common.LogsExplorerNavigationParams.query", "type": "CompoundType", "tags": [], "label": "query", @@ -168,13 +168,13 @@ }, " | undefined" ], - "path": "packages/deeplinks/observability/locators/log_explorer.ts", + "path": "packages/deeplinks/observability/locators/logs_explorer.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "@kbn/deeplinks-observability", - "id": "def-common.LogExplorerNavigationParams.columns", + "id": "def-common.LogsExplorerNavigationParams.columns", "type": "Array", "tags": [], "label": "columns", @@ -184,13 +184,13 @@ "signature": [ "string[] | undefined" ], - "path": "packages/deeplinks/observability/locators/log_explorer.ts", + "path": "packages/deeplinks/observability/locators/logs_explorer.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "@kbn/deeplinks-observability", - "id": "def-common.LogExplorerNavigationParams.filters", + "id": "def-common.LogsExplorerNavigationParams.filters", "type": "Array", "tags": [], "label": "filters", @@ -207,13 +207,13 @@ }, "[] | undefined" ], - "path": "packages/deeplinks/observability/locators/log_explorer.ts", + "path": "packages/deeplinks/observability/locators/logs_explorer.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "@kbn/deeplinks-observability", - "id": "def-common.LogExplorerNavigationParams.filterControls", + "id": "def-common.LogsExplorerNavigationParams.filterControls", "type": "Object", "tags": [], "label": "filterControls", @@ -230,7 +230,7 @@ }, " | undefined" ], - "path": "packages/deeplinks/observability/locators/log_explorer.ts", + "path": "packages/deeplinks/observability/locators/logs_explorer.ts", "deprecated": false, "trackAdoption": false } @@ -239,18 +239,18 @@ }, { "parentPluginId": "@kbn/deeplinks-observability", - "id": "def-common.ObservabilityLogExplorerLocationState", + "id": "def-common.ObservabilityLogsExplorerLocationState", "type": "Interface", "tags": [], - "label": "ObservabilityLogExplorerLocationState", + "label": "ObservabilityLogsExplorerLocationState", "description": [], "signature": [ { "pluginId": "@kbn/deeplinks-observability", "scope": "common", "docId": "kibKbnDeeplinksObservabilityPluginApi", - "section": "def-common.ObservabilityLogExplorerLocationState", - "text": "ObservabilityLogExplorerLocationState" + "section": "def-common.ObservabilityLogsExplorerLocationState", + "text": "ObservabilityLogsExplorerLocationState" }, " extends ", { @@ -261,13 +261,13 @@ "text": "SerializableRecord" } ], - "path": "packages/deeplinks/observability/locators/observability_log_explorer.ts", + "path": "packages/deeplinks/observability/locators/observability_logs_explorer.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "@kbn/deeplinks-observability", - "id": "def-common.ObservabilityLogExplorerLocationState.origin", + "id": "def-common.ObservabilityLogsExplorerLocationState.origin", "type": "Object", "tags": [], "label": "origin", @@ -275,7 +275,7 @@ "signature": [ "{ id: \"application-log-onboarding\"; } | undefined" ], - "path": "packages/deeplinks/observability/locators/observability_log_explorer.ts", + "path": "packages/deeplinks/observability/locators/observability_logs_explorer.ts", "deprecated": false, "trackAdoption": false } @@ -353,7 +353,7 @@ "text": "DatasetLocatorParams" } ], - "path": "packages/deeplinks/observability/locators/observability_log_explorer.ts", + "path": "packages/deeplinks/observability/locators/observability_logs_explorer.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -369,7 +369,7 @@ "signature": [ "string | undefined" ], - "path": "packages/deeplinks/observability/locators/observability_log_explorer.ts", + "path": "packages/deeplinks/observability/locators/observability_logs_explorer.ts", "deprecated": false, "trackAdoption": false }, @@ -382,7 +382,7 @@ "description": [ "\nDataset name to be selected.\nex: system.syslog" ], - "path": "packages/deeplinks/observability/locators/observability_log_explorer.ts", + "path": "packages/deeplinks/observability/locators/observability_logs_explorer.ts", "deprecated": false, "trackAdoption": false } @@ -402,7 +402,7 @@ "signature": [ "\"ALL_DATASETS_LOCATOR\"" ], - "path": "packages/deeplinks/observability/locators/observability_log_explorer.ts", + "path": "packages/deeplinks/observability/locators/observability_logs_explorer.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -419,19 +419,19 @@ "pluginId": "@kbn/deeplinks-observability", "scope": "common", "docId": "kibKbnDeeplinksObservabilityPluginApi", - "section": "def-common.LogExplorerNavigationParams", - "text": "LogExplorerNavigationParams" + "section": "def-common.LogsExplorerNavigationParams", + "text": "LogsExplorerNavigationParams" }, " & ", { "pluginId": "@kbn/deeplinks-observability", "scope": "common", "docId": "kibKbnDeeplinksObservabilityPluginApi", - "section": "def-common.ObservabilityLogExplorerLocationState", - "text": "ObservabilityLogExplorerLocationState" + "section": "def-common.ObservabilityLogsExplorerLocationState", + "text": "ObservabilityLogsExplorerLocationState" } ], - "path": "packages/deeplinks/observability/locators/observability_log_explorer.ts", + "path": "packages/deeplinks/observability/locators/observability_logs_explorer.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -463,19 +463,19 @@ "pluginId": "@kbn/deeplinks-observability", "scope": "common", "docId": "kibKbnDeeplinksObservabilityPluginApi", - "section": "def-common.LogExplorerNavigationParams", - "text": "LogExplorerNavigationParams" + "section": "def-common.LogsExplorerNavigationParams", + "text": "LogsExplorerNavigationParams" }, " & ", { "pluginId": "@kbn/deeplinks-observability", "scope": "common", "docId": "kibKbnDeeplinksObservabilityPluginApi", - "section": "def-common.ObservabilityLogExplorerLocationState", - "text": "ObservabilityLogExplorerLocationState" + "section": "def-common.ObservabilityLogsExplorerLocationState", + "text": "ObservabilityLogsExplorerLocationState" } ], - "path": "packages/deeplinks/observability/locators/observability_log_explorer.ts", + "path": "packages/deeplinks/observability/locators/observability_logs_explorer.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -520,7 +520,7 @@ }, " | undefined; }" ], - "path": "packages/deeplinks/observability/locators/log_explorer.ts", + "path": "packages/deeplinks/observability/locators/logs_explorer.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -535,37 +535,37 @@ "signature": [ "{ mode: \"include\"; values: string[]; }" ], - "path": "packages/deeplinks/observability/locators/log_explorer.ts", + "path": "packages/deeplinks/observability/locators/logs_explorer.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false }, { "parentPluginId": "@kbn/deeplinks-observability", - "id": "def-common.LOG_EXPLORER_LOCATOR_ID", + "id": "def-common.LOGS_APP_ID", "type": "string", "tags": [], - "label": "LOG_EXPLORER_LOCATOR_ID", + "label": "LOGS_APP_ID", "description": [], "signature": [ - "\"LOG_EXPLORER_LOCATOR\"" + "\"logs\"" ], - "path": "packages/deeplinks/observability/locators/log_explorer.ts", + "path": "packages/deeplinks/observability/constants.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false }, { "parentPluginId": "@kbn/deeplinks-observability", - "id": "def-common.LOGS_APP_ID", + "id": "def-common.LOGS_EXPLORER_LOCATOR_ID", "type": "string", "tags": [], - "label": "LOGS_APP_ID", + "label": "LOGS_EXPLORER_LOCATOR_ID", "description": [], "signature": [ - "\"logs\"" + "\"LOGS_EXPLORER_LOCATOR\"" ], - "path": "packages/deeplinks/observability/constants.ts", + "path": "packages/deeplinks/observability/locators/logs_explorer.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -640,7 +640,7 @@ "signature": [ "{ pause: boolean; value: number; }" ], - "path": "packages/deeplinks/observability/locators/log_explorer.ts", + "path": "packages/deeplinks/observability/locators/logs_explorer.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -655,7 +655,7 @@ "signature": [ "\"SINGLE_DATASET_LOCATOR\"" ], - "path": "packages/deeplinks/observability/locators/observability_log_explorer.ts", + "path": "packages/deeplinks/observability/locators/observability_logs_explorer.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false diff --git a/api_docs/kbn_deeplinks_observability.mdx b/api_docs/kbn_deeplinks_observability.mdx index e9ee5cc10fe8a..0a2ae4ff3d01f 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-02-05 +date: 2024-02-07 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 04418131486ce..322e7f952dee2 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-search'] --- import kbnDeeplinksSearchObj from './kbn_deeplinks_search.devdocs.json'; diff --git a/api_docs/kbn_default_nav_analytics.mdx b/api_docs/kbn_default_nav_analytics.mdx index d3a3a59d1fd87..ec04c8d3098d2 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-02-05 +date: 2024-02-07 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 b67b78cdc98c9..242fb6d69d83d 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-02-05 +date: 2024-02-07 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 41cc7079fc30e..9ec2da49f04a3 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-02-05 +date: 2024-02-07 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 dcd583132287b..d2ceda766c057 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-02-05 +date: 2024-02-07 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 7567dc7b9fce9..68353954d9e68 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-02-05 +date: 2024-02-07 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 870bcdac50390..9c17d43669d82 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-02-05 +date: 2024-02-07 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 d7c9213994ba2..380b09055b986 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-02-05 +date: 2024-02-07 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 419ce240c97da..719e14e2f62fa 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-02-05 +date: 2024-02-07 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 d61a0cdfe035a..39d3a3891f4a3 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/discover-utils'] --- import kbnDiscoverUtilsObj from './kbn_discover_utils.devdocs.json'; diff --git a/api_docs/kbn_doc_links.mdx b/api_docs/kbn_doc_links.mdx index c0a97a9c0c403..dcfc6e4a6a4a5 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/doc-links'] --- import kbnDocLinksObj from './kbn_doc_links.devdocs.json'; diff --git a/api_docs/kbn_docs_utils.mdx b/api_docs/kbn_docs_utils.mdx index e821f7344268c..49bc90f53ca0f 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/docs-utils'] --- import kbnDocsUtilsObj from './kbn_docs_utils.devdocs.json'; diff --git a/api_docs/kbn_dom_drag_drop.mdx b/api_docs/kbn_dom_drag_drop.mdx index 17dcc241c5add..a019b3bb74be3 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dom-drag-drop'] --- import kbnDomDragDropObj from './kbn_dom_drag_drop.devdocs.json'; diff --git a/api_docs/kbn_ebt_tools.mdx b/api_docs/kbn_ebt_tools.mdx index 60771cb52e782..6282aa3edfae7 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ebt-tools'] --- import kbnEbtToolsObj from './kbn_ebt_tools.devdocs.json'; diff --git a/api_docs/kbn_ecs.mdx b/api_docs/kbn_ecs.mdx index feead00308549..25e32dfe402b6 100644 --- a/api_docs/kbn_ecs.mdx +++ b/api_docs/kbn_ecs.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ecs title: "@kbn/ecs" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ecs plugin -date: 2024-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ecs'] --- import kbnEcsObj from './kbn_ecs.devdocs.json'; diff --git a/api_docs/kbn_ecs_data_quality_dashboard.mdx b/api_docs/kbn_ecs_data_quality_dashboard.mdx index b4fc7379d28a9..41867eca525cb 100644 --- a/api_docs/kbn_ecs_data_quality_dashboard.mdx +++ b/api_docs/kbn_ecs_data_quality_dashboard.mdx @@ -8,14 +8,14 @@ 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ecs-data-quality-dashboard'] --- import kbnEcsDataQualityDashboardObj from './kbn_ecs_data_quality_dashboard.devdocs.json'; -Contact [@elastic/security-threat-hunting-investigations](https://github.com/orgs/elastic/teams/security-threat-hunting-investigations) for questions regarding this plugin. +Contact [@elastic/security-threat-hunting-explore](https://github.com/orgs/elastic/teams/security-threat-hunting-explore) for questions regarding this plugin. **Code health stats** diff --git a/api_docs/kbn_elastic_agent_utils.mdx b/api_docs/kbn_elastic_agent_utils.mdx index 61f0c2f65bed2..adfdd843f70ef 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-02-05 +date: 2024-02-07 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 d111fb431c57b..ae338e411547c 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-02-05 +date: 2024-02-07 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.mdx b/api_docs/kbn_elastic_assistant_common.mdx index ad50dd8bdd40f..20aaa71760329 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/elastic-assistant-common'] --- import kbnElasticAssistantCommonObj from './kbn_elastic_assistant_common.devdocs.json'; diff --git a/api_docs/kbn_es.mdx b/api_docs/kbn_es.mdx index e13292205cc44..8b252d558d159 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es'] --- import kbnEsObj from './kbn_es.devdocs.json'; diff --git a/api_docs/kbn_es_archiver.mdx b/api_docs/kbn_es_archiver.mdx index 97df4332dbea4..efa1967e1ac98 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-02-05 +date: 2024-02-07 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 fac7221801450..a25b94c269753 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-errors'] --- import kbnEsErrorsObj from './kbn_es_errors.devdocs.json'; diff --git a/api_docs/kbn_es_query.devdocs.json b/api_docs/kbn_es_query.devdocs.json index d1450a687142d..0cb799db44c57 100644 --- a/api_docs/kbn_es_query.devdocs.json +++ b/api_docs/kbn_es_query.devdocs.json @@ -3190,6 +3190,72 @@ "returnComment": [], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/es-query", + "id": "def-common.isOfEsqlQueryType", + "type": "Function", + "tags": [], + "label": "isOfEsqlQueryType", + "description": [ + "\nTrue if the query is of type AggregateQuery and is of type esql, false otherwise." + ], + "signature": [ + "(query: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Query", + "text": "Query" + }, + " | ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.AggregateQuery", + "text": "AggregateQuery" + }, + " | { [key: string]: any; } | undefined) => boolean" + ], + "path": "packages/kbn-es-query/src/es_query/es_aggregate_query.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/es-query", + "id": "def-common.isOfEsqlQueryType.$1", + "type": "CompoundType", + "tags": [], + "label": "query", + "description": [], + "signature": [ + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Query", + "text": "Query" + }, + " | ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.AggregateQuery", + "text": "AggregateQuery" + }, + " | { [key: string]: any; } | undefined" + ], + "path": "packages/kbn-es-query/src/es_query/es_aggregate_query.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/es-query", "id": "def-common.isOfQueryType", diff --git a/api_docs/kbn_es_query.mdx b/api_docs/kbn_es_query.mdx index c848550fa8fde..b1d831e76150b 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-query'] --- import kbnEsQueryObj from './kbn_es_query.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/k | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 261 | 1 | 201 | 15 | +| 263 | 1 | 202 | 15 | ## Common diff --git a/api_docs/kbn_es_types.devdocs.json b/api_docs/kbn_es_types.devdocs.json index 91d8256af2de7..5ace284b455cf 100644 --- a/api_docs/kbn_es_types.devdocs.json +++ b/api_docs/kbn_es_types.devdocs.json @@ -199,6 +199,20 @@ "path": "packages/kbn-es-types/src/search.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "@kbn/es-types", + "id": "def-common.ESQLSearchParams.dropNullColumns", + "type": "CompoundType", + "tags": [], + "label": "dropNullColumns", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "packages/kbn-es-types/src/search.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false diff --git a/api_docs/kbn_es_types.mdx b/api_docs/kbn_es_types.mdx index 409922791910f..48b0328864cff 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-types'] --- import kbnEsTypesObj from './kbn_es_types.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 31 | 0 | 31 | 0 | +| 32 | 0 | 32 | 0 | ## Common diff --git a/api_docs/kbn_eslint_plugin_imports.mdx b/api_docs/kbn_eslint_plugin_imports.mdx index 658b183f68dde..b7e8a2218fdb7 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-02-05 +date: 2024-02-07 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_utils.devdocs.json b/api_docs/kbn_esql_utils.devdocs.json index 2658a321ae1e8..87874a975a0cf 100644 --- a/api_docs/kbn_esql_utils.devdocs.json +++ b/api_docs/kbn_esql_utils.devdocs.json @@ -89,6 +89,69 @@ "returnComment": [], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/esql-utils", + "id": "def-common.getIndexForESQLQuery", + "type": "Function", + "tags": [], + "label": "getIndexForESQLQuery", + "description": [ + "\nThis can be used to get an initial index for a default ES|QL query.\nCould be used during onboarding when data views to get a better index are not yet available.\nCan be used in combination with {@link getESQLAdHocDataview} to create a dataview for the index." + ], + "signature": [ + "(deps: { dataViews: { getIndices: (props: { pattern: string; showAllIndices?: boolean | undefined; isRollupIndex: (indexName: string) => boolean; }) => Promise<", + { + "pluginId": "dataViews", + "scope": "public", + "docId": "kibDataViewsPluginApi", + "section": "def-public.MatchedItem", + "text": "MatchedItem" + }, + "[]>; }; }) => Promise" + ], + "path": "packages/kbn-esql-utils/src/utils/get_esql_adhoc_dataview.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/esql-utils", + "id": "def-common.getIndexForESQLQuery.$1", + "type": "Object", + "tags": [], + "label": "deps", + "description": [], + "path": "packages/kbn-esql-utils/src/utils/get_esql_adhoc_dataview.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/esql-utils", + "id": "def-common.getIndexForESQLQuery.$1.dataViews", + "type": "Object", + "tags": [], + "label": "dataViews", + "description": [], + "signature": [ + "{ getIndices: (props: { pattern: string; showAllIndices?: boolean | undefined; isRollupIndex: (indexName: string) => boolean; }) => Promise<", + { + "pluginId": "dataViews", + "scope": "public", + "docId": "kibDataViewsPluginApi", + "section": "def-public.MatchedItem", + "text": "MatchedItem" + }, + "[]>; }" + ], + "path": "packages/kbn-esql-utils/src/utils/get_esql_adhoc_dataview.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/esql-utils", "id": "def-common.getIndexPatternFromESQLQuery", diff --git a/api_docs/kbn_esql_utils.mdx b/api_docs/kbn_esql_utils.mdx index b7be59e519d0e..2a064b15d03b1 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/esql-utils'] --- import kbnEsqlUtilsObj from './kbn_esql_utils.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 | |-------------------|-----------|------------------------|-----------------| -| 11 | 0 | 11 | 0 | +| 14 | 0 | 13 | 0 | ## Common diff --git a/api_docs/kbn_event_annotation_common.mdx b/api_docs/kbn_event_annotation_common.mdx index a9e03770d4338..72293fae53ab6 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-02-05 +date: 2024-02-07 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 cdba952222cd3..6497854ede2bb 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-02-05 +date: 2024-02-07 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 17a9fbf8269cd..289cdd0a644da 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/expandable-flyout'] --- import kbnExpandableFlyoutObj from './kbn_expandable_flyout.devdocs.json'; diff --git a/api_docs/kbn_field_types.devdocs.json b/api_docs/kbn_field_types.devdocs.json index 3aa7c53ebf9c9..8719c9d696b1f 100644 --- a/api_docs/kbn_field_types.devdocs.json +++ b/api_docs/kbn_field_types.devdocs.json @@ -173,6 +173,46 @@ "returnComment": [], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/field-types", + "id": "def-common.esFieldTypeToKibanaFieldType", + "type": "Function", + "tags": [], + "label": "esFieldTypeToKibanaFieldType", + "description": [], + "signature": [ + "(type: string) => ", + { + "pluginId": "@kbn/field-types", + "scope": "common", + "docId": "kibKbnFieldTypesPluginApi", + "section": "def-common.KBN_FIELD_TYPES", + "text": "KBN_FIELD_TYPES" + } + ], + "path": "packages/kbn-field-types/src/kbn_field_types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/field-types", + "id": "def-common.esFieldTypeToKibanaFieldType.$1", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + "string" + ], + "path": "packages/kbn-field-types/src/kbn_field_types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/field-types", "id": "def-common.getFilterableKbnTypeNames", diff --git a/api_docs/kbn_field_types.mdx b/api_docs/kbn_field_types.mdx index e46a32da4121f..735ece03762c2 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/field-types'] --- import kbnFieldTypesObj from './kbn_field_types.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 | |-------------------|-----------|------------------------|-----------------| -| 20 | 0 | 16 | 0 | +| 22 | 0 | 18 | 0 | ## Common diff --git a/api_docs/kbn_field_utils.mdx b/api_docs/kbn_field_utils.mdx index 64f893eea3ae6..8b6cc0a5b1f78 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-02-05 +date: 2024-02-07 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 60993da2458ba..2fea6be43d4c8 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/find-used-node-modules'] --- import kbnFindUsedNodeModulesObj from './kbn_find_used_node_modules.devdocs.json'; diff --git a/api_docs/kbn_ftr_common_functional_services.mdx b/api_docs/kbn_ftr_common_functional_services.mdx index a1c72db7b99d9..3f128871c2abc 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ftr-common-functional-services'] --- import kbnFtrCommonFunctionalServicesObj from './kbn_ftr_common_functional_services.devdocs.json'; diff --git a/api_docs/kbn_ftr_common_functional_ui_services.mdx b/api_docs/kbn_ftr_common_functional_ui_services.mdx index 7d82c9051ad27..160dcb36b94d2 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-02-05 +date: 2024-02-07 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 3b621ad02bd12..34509d60e8ec1 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-02-05 +date: 2024-02-07 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 8f0965e19c06c..9913901a3f50a 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-02-05 +date: 2024-02-07 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.devdocs.json b/api_docs/kbn_generate_csv.devdocs.json index 2c04fe339b8d4..95e83e2c2293d 100644 --- a/api_docs/kbn_generate_csv.devdocs.json +++ b/api_docs/kbn_generate_csv.devdocs.json @@ -84,6 +84,21 @@ "id": "def-server.CsvESQLGenerator.Unnamed.$3", "type": "Object", "tags": [], + "label": "taskInstanceFields", + "description": [], + "signature": [ + "TaskInstanceFields" + ], + "path": "packages/kbn-generate-csv/src/generate_csv_esql.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/generate-csv", + "id": "def-server.CsvESQLGenerator.Unnamed.$4", + "type": "Object", + "tags": [], "label": "clients", "description": [], "signature": [ @@ -96,7 +111,7 @@ }, { "parentPluginId": "@kbn/generate-csv", - "id": "def-server.CsvESQLGenerator.Unnamed.$4", + "id": "def-server.CsvESQLGenerator.Unnamed.$5", "type": "Object", "tags": [], "label": "cancellationToken", @@ -117,7 +132,7 @@ }, { "parentPluginId": "@kbn/generate-csv", - "id": "def-server.CsvESQLGenerator.Unnamed.$5", + "id": "def-server.CsvESQLGenerator.Unnamed.$6", "type": "Object", "tags": [], "label": "logger", @@ -138,7 +153,7 @@ }, { "parentPluginId": "@kbn/generate-csv", - "id": "def-server.CsvESQLGenerator.Unnamed.$6", + "id": "def-server.CsvESQLGenerator.Unnamed.$7", "type": "Object", "tags": [], "label": "stream", diff --git a/api_docs/kbn_generate_csv.mdx b/api_docs/kbn_generate_csv.mdx index 46af496e8f49d..eb4420f2bfce6 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate-csv'] --- import kbnGenerateCsvObj from './kbn_generate_csv.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sh | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 25 | 0 | 25 | 1 | +| 26 | 0 | 26 | 1 | ## Server diff --git a/api_docs/kbn_guided_onboarding.mdx b/api_docs/kbn_guided_onboarding.mdx index bc9e156d64bbc..573755f508a8d 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-02-05 +date: 2024-02-07 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 d9a64fa0ca904..a084cd640a599 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-02-05 +date: 2024-02-07 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 30acafcc924d5..ec6163cbb74fd 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-02-05 +date: 2024-02-07 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 9266523665abd..cafa671eba423 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-02-05 +date: 2024-02-07 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 9a9d6d9fec193..93a390b1aaacc 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-02-05 +date: 2024-02-07 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 8bbc363c8e298..81df1c1ced89a 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-02-05 +date: 2024-02-07 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 0c0d3cca83775..120ce9b731142 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-02-05 +date: 2024-02-07 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 ec02cec26a25b..751c1c5a7f700 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-02-05 +date: 2024-02-07 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 6f7d614fe1ddf..bf7579288297a 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/import-resolver'] --- import kbnImportResolverObj from './kbn_import_resolver.devdocs.json'; diff --git a/api_docs/kbn_infra_forge.mdx b/api_docs/kbn_infra_forge.mdx index 66773958a5627..23fdabc43b592 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-02-05 +date: 2024-02-07 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 05f1543be0e01..8d076669808d0 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-02-05 +date: 2024-02-07 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 a270bf93413c7..a5c74c37a920f 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/io-ts-utils'] --- import kbnIoTsUtilsObj from './kbn_io_ts_utils.devdocs.json'; diff --git a/api_docs/kbn_jest_serializers.mdx b/api_docs/kbn_jest_serializers.mdx index fbe636d875537..9a0baf4aa8c38 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-02-05 +date: 2024-02-07 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 cf153944e5d1b..262147a7a3e1c 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-02-05 +date: 2024-02-07 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 2873faf48c434..0432233458377 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/json-ast'] --- import kbnJsonAstObj from './kbn_json_ast.devdocs.json'; diff --git a/api_docs/kbn_kibana_manifest_schema.mdx b/api_docs/kbn_kibana_manifest_schema.mdx index f51fe26d520ec..9f12ba2aef382 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/kibana-manifest-schema'] --- import kbnKibanaManifestSchemaObj from './kbn_kibana_manifest_schema.devdocs.json'; diff --git a/api_docs/kbn_language_documentation_popover.mdx b/api_docs/kbn_language_documentation_popover.mdx index 9cdc26b14d842..08c19c2aef814 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-02-05 +date: 2024-02-07 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 d2309aeb51440..03bb0eb192689 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-02-05 +date: 2024-02-07 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 046f2fe235fc7..a9bc9f0fcc49a 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-02-05 +date: 2024-02-07 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 2294b62c01098..9dbe6ce120588 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-02-05 +date: 2024-02-07 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 7c5624b040e0a..ce83ceb399c83 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-02-05 +date: 2024-02-07 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 d46ed305b9e2d..93d8f78ae67cb 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-02-05 +date: 2024-02-07 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 639c239481927..958857f7ca63f 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/managed-vscode-config'] --- import kbnManagedVscodeConfigObj from './kbn_managed_vscode_config.devdocs.json'; diff --git a/api_docs/kbn_management_cards_navigation.mdx b/api_docs/kbn_management_cards_navigation.mdx index 4318cc66c79e5..79cfc21a67898 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-02-05 +date: 2024-02-07 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 ca549d989d17d..3cfca3b87651d 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-02-05 +date: 2024-02-07 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 fff40da200291..332a136ca42bc 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-02-05 +date: 2024-02-07 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 c1646e99684d3..e9f9ded87a480 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-02-05 +date: 2024-02-07 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 32875523b7bdf..ebf02d9f74f45 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-02-05 +date: 2024-02-07 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 428320d025a9d..463c2a3a78974 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-02-05 +date: 2024-02-07 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 4c930a5e938b7..faf8e0b173dd5 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-02-05 +date: 2024-02-07 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 26d5541422881..88b1e4ed9b9c8 100644 --- a/api_docs/kbn_management_settings_ids.devdocs.json +++ b/api_docs/kbn_management_settings_ids.devdocs.json @@ -1312,6 +1312,21 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.OBSERVABILITY_ENABLE_INFRASTRUCTURE_HOSTS_CUSTOM_DASHBOARDS_ID", + "type": "string", + "tags": [], + "label": "OBSERVABILITY_ENABLE_INFRASTRUCTURE_HOSTS_CUSTOM_DASHBOARDS_ID", + "description": [], + "signature": [ + "\"observability:enableInfrastructureHostsCustomDashboards\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/management-settings-ids", "id": "def-common.OBSERVABILITY_ENABLE_INFRASTRUCTURE_HOSTS_VIEW_ID", diff --git a/api_docs/kbn_management_settings_ids.mdx b/api_docs/kbn_management_settings_ids.mdx index 102de8a5cbfd4..c3f9577e72bbb 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-02-05 +date: 2024-02-07 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/platform-deployment-management](https: | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 133 | 0 | 131 | 0 | +| 134 | 0 | 132 | 0 | ## Common diff --git a/api_docs/kbn_management_settings_section_registry.mdx b/api_docs/kbn_management_settings_section_registry.mdx index 0f5e63dc479b0..ca62c84ccce54 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-02-05 +date: 2024-02-07 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 472a2f71f079e..0eaf07f4388ca 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-02-05 +date: 2024-02-07 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 bea4234709f9c..b78b135f68c1e 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-02-05 +date: 2024-02-07 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 e02027a27d53e..b120fd79c6b6e 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-02-05 +date: 2024-02-07 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 339a9719bd8c1..4c2fd9c354c46 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-02-05 +date: 2024-02-07 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 6ef1b3797b6fd..3156a0b5253b6 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/maps-vector-tile-utils'] --- import kbnMapsVectorTileUtilsObj from './kbn_maps_vector_tile_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_agg_utils.mdx b/api_docs/kbn_ml_agg_utils.mdx index bb69c5062e659..65fa63b4b0fa3 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-agg-utils'] --- import kbnMlAggUtilsObj from './kbn_ml_agg_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_anomaly_utils.mdx b/api_docs/kbn_ml_anomaly_utils.mdx index 91f4911c70305..8f3c352f2bdc2 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-02-05 +date: 2024-02-07 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 07da0ad688ab8..96fea890ba888 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-02-05 +date: 2024-02-07 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 df5f8461e47c4..371bb202c9add 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-02-05 +date: 2024-02-07 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 c25532ad39e23..80c63d0937b1e 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-02-05 +date: 2024-02-07 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 d28ef7e506927..442f2c67ec3a3 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-02-05 +date: 2024-02-07 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 423ee518563ca..2af285926a1b8 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-02-05 +date: 2024-02-07 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 cbce0522f0654..bd2cdbfa3b20c 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-02-05 +date: 2024-02-07 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 2fba4fa4cecb8..d38c3ca14864e 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-02-05 +date: 2024-02-07 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 2609cd6ef08ba..aa4eccea26339 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-02-05 +date: 2024-02-07 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 c38be23ab9e44..9a43b5dd01fdb 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-02-05 +date: 2024-02-07 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 01686550224c3..cc059d71e8c43 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-02-05 +date: 2024-02-07 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 0da106f033886..5ce23fd69cc05 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-02-05 +date: 2024-02-07 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 82cd7c4fad548..6ede7c8561489 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-02-05 +date: 2024-02-07 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 3aa236fd75dd7..8bfbfb5ac2ae2 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-02-05 +date: 2024-02-07 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 4f60ca2821c06..f7d711f7a4a72 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-02-05 +date: 2024-02-07 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 2fa46f5817330..be07b8ea90149 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-02-05 +date: 2024-02-07 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 237e6466eee28..9a40e0d51ef56 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-02-05 +date: 2024-02-07 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 9bd3212a1dc49..11d18ded956dc 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-02-05 +date: 2024-02-07 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 ef4859a668a4a..273ba65c6eb28 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-02-05 +date: 2024-02-07 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 c55b4b748f9d9..9fd0dcbb0d144 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-02-05 +date: 2024-02-07 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 ec7301fd5606a..5d2919ca314a6 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-string-hash'] --- import kbnMlStringHashObj from './kbn_ml_string_hash.devdocs.json'; diff --git a/api_docs/kbn_ml_trained_models_utils.mdx b/api_docs/kbn_ml_trained_models_utils.mdx index ebd1b266f791f..47fb2ddfcd116 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-trained-models-utils'] --- import kbnMlTrainedModelsUtilsObj from './kbn_ml_trained_models_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_ui_actions.mdx b/api_docs/kbn_ml_ui_actions.mdx index 75f08091f0737..d82e91afb17b9 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-02-05 +date: 2024-02-07 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 c3ae366184658..7b6df5cca7723 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-02-05 +date: 2024-02-07 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 d1409e35bd079..03d0154309965 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-02-05 +date: 2024-02-07 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 1abfb94196fe6..811e3a48e3bfb 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-02-05 +date: 2024-02-07 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 1494c7622b48d..bd7fc4c86ab04 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-02-05 +date: 2024-02-07 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 546fc39004295..5ee6ac304825a 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-02-05 +date: 2024-02-07 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_test_data.mdx b/api_docs/kbn_observability_alerting_test_data.mdx index 93eb9e79ecc68..04a28191684b5 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-02-05 +date: 2024-02-07 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 bd9168c43db14..8231ab4d5566e 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-02-05 +date: 2024-02-07 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.mdx b/api_docs/kbn_openapi_bundler.mdx index 904a23c47d8bd..b0bf138817dc0 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-02-05 +date: 2024-02-07 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 cc1ae8f67e120..14b1f907daa3c 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/openapi-generator'] --- import kbnOpenapiGeneratorObj from './kbn_openapi_generator.devdocs.json'; diff --git a/api_docs/kbn_optimizer.mdx b/api_docs/kbn_optimizer.mdx index e0343824e0e56..981ee63b9fe7d 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/optimizer'] --- import kbnOptimizerObj from './kbn_optimizer.devdocs.json'; diff --git a/api_docs/kbn_optimizer_webpack_helpers.mdx b/api_docs/kbn_optimizer_webpack_helpers.mdx index 65f74a23ecf92..71150e3489582 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-02-05 +date: 2024-02-07 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 7626897bcbfdb..ecb0af21bf21b 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-02-05 +date: 2024-02-07 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 8d947e024746f..84be618cee3f5 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-02-05 +date: 2024-02-07 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 ce44f7284370e..a34bab2f85fc8 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-02-05 +date: 2024-02-07 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.mdx b/api_docs/kbn_plugin_check.mdx index fe1477871bb31..c712168992dcd 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-check'] --- import kbnPluginCheckObj from './kbn_plugin_check.devdocs.json'; diff --git a/api_docs/kbn_plugin_generator.mdx b/api_docs/kbn_plugin_generator.mdx index 2e41f39dd1166..9c66e48a438a5 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-generator'] --- import kbnPluginGeneratorObj from './kbn_plugin_generator.devdocs.json'; diff --git a/api_docs/kbn_plugin_helpers.mdx b/api_docs/kbn_plugin_helpers.mdx index f4687a7577328..39dbef24ba4d8 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-helpers'] --- import kbnPluginHelpersObj from './kbn_plugin_helpers.devdocs.json'; diff --git a/api_docs/kbn_presentation_containers.mdx b/api_docs/kbn_presentation_containers.mdx index 3a74c12dce30e..a44d44c1edeaa 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/presentation-containers'] --- import kbnPresentationContainersObj from './kbn_presentation_containers.devdocs.json'; diff --git a/api_docs/kbn_presentation_library.mdx b/api_docs/kbn_presentation_library.mdx index 43a6b018b0a65..c151a8d335661 100644 --- a/api_docs/kbn_presentation_library.mdx +++ b/api_docs/kbn_presentation_library.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-presentation-library title: "@kbn/presentation-library" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/presentation-library plugin -date: 2024-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/presentation-library'] --- import kbnPresentationLibraryObj from './kbn_presentation_library.devdocs.json'; diff --git a/api_docs/kbn_presentation_publishing.mdx b/api_docs/kbn_presentation_publishing.mdx index 24ecc2e55f1fa..1a149f1ea300c 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-02-05 +date: 2024-02-07 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 05d8e3a4194b7..b8b1337aa4e64 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-02-05 +date: 2024-02-07 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 b37647c10bc3a..0501b7bf93dca 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-02-05 +date: 2024-02-07 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 87cd914969868..c1caca7f748dd 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-field'] --- import kbnReactFieldObj from './kbn_react_field.devdocs.json'; diff --git a/api_docs/kbn_react_kibana_context_common.mdx b/api_docs/kbn_react_kibana_context_common.mdx index 52f70b319f850..69a9dae1a196b 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-02-05 +date: 2024-02-07 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 f334818958b9f..740aa50508175 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-02-05 +date: 2024-02-07 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 8b6f42715eb14..5a735e4cf3de0 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-02-05 +date: 2024-02-07 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 69a7780bb98a2..266795961f42c 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-02-05 +date: 2024-02-07 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 53cfede7a0661..52e6c4cfd2c67 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-02-05 +date: 2024-02-07 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 2db09ede7b702..75c8572a39049 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-kibana-mount'] --- import kbnReactKibanaMountObj from './kbn_react_kibana_mount.devdocs.json'; diff --git a/api_docs/kbn_repo_file_maps.mdx b/api_docs/kbn_repo_file_maps.mdx index b07d618f3b5cb..7a05363a52145 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-02-05 +date: 2024-02-07 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 bf8381de5f363..1e894cf273c46 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-02-05 +date: 2024-02-07 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 4849e8ba9b404..bc95c05cf2a13 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-02-05 +date: 2024-02-07 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 f05c8fc0c3a08..6261995e2152c 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-02-05 +date: 2024-02-07 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 a5dc0d9057d21..ffc494375b77a 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-common'] --- import kbnReportingCommonObj from './kbn_reporting_common.devdocs.json'; diff --git a/api_docs/kbn_reporting_export_types_csv.mdx b/api_docs/kbn_reporting_export_types_csv.mdx index a41105ea1852f..0925fa8008050 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-02-05 +date: 2024-02-07 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 5d9e5bb5b7181..e9bf662be5cb9 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-02-05 +date: 2024-02-07 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 f3dec9b7c5738..0508c69fc9ff1 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-02-05 +date: 2024-02-07 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 df58c8ca51f6c..dd9a403c7bd88 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-02-05 +date: 2024-02-07 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 7f910e8cc4e7d..00413562e36a2 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-02-05 +date: 2024-02-07 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 2deb4def3bb3f..3703793a166bc 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-02-05 +date: 2024-02-07 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 1c5da1ac85dbd..8920bec39bf6a 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-02-05 +date: 2024-02-07 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 9210d365f381c..ad51a406bd919 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-02-05 +date: 2024-02-07 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 9bebedb25020c..916ad049b16d6 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-02-05 +date: 2024-02-07 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 e64205a7eb1c2..437df4dce017d 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/resizable-layout'] --- import kbnResizableLayoutObj from './kbn_resizable_layout.devdocs.json'; diff --git a/api_docs/kbn_rison.mdx b/api_docs/kbn_rison.mdx index b92d6661e383c..5b05a13a7ee9c 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rison'] --- import kbnRisonObj from './kbn_rison.devdocs.json'; diff --git a/api_docs/kbn_router_utils.mdx b/api_docs/kbn_router_utils.mdx index 6a84261820c86..1cef1db778563 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-02-05 +date: 2024-02-07 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 da600a21264f7..9b4d6f908ac84 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-02-05 +date: 2024-02-07 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 b5d4960e753ed..27a4f1fb5ddfa 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-02-05 +date: 2024-02-07 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 bef008fba9349..fe319b552d26e 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-02-05 +date: 2024-02-07 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 ff21d8a298718..980026cb46f0e 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-02-05 +date: 2024-02-07 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 8e4485adab1a9..e0e95a1b88c2e 100644 --- a/api_docs/kbn_search_connectors.devdocs.json +++ b/api_docs/kbn_search_connectors.devdocs.json @@ -1350,8 +1350,8 @@ "text": "ElasticsearchClient" }, ", connectorId: string, isNative: boolean) => Promise<", - "UpdateResponse", - ">" + "Result", + ">" ], "path": "packages/kbn-search-connectors/lib/update_native.ts", "deprecated": false, @@ -1898,7 +1898,7 @@ "text": "ElasticsearchClient" }, ", connectorId: string, indexName: string) => Promise<", - "WriteResponseBase", + "Result", ">" ], "path": "packages/kbn-search-connectors/lib/update_connector_index_name.ts", @@ -2258,7 +2258,7 @@ "text": "ConnectorStatus" }, ") => Promise<", - "WriteResponseBase", + "Result", ">" ], "path": "packages/kbn-search-connectors/lib/update_connector_status.ts", diff --git a/api_docs/kbn_search_connectors.mdx b/api_docs/kbn_search_connectors.mdx index 07e39017a4bc7..5b48e6afa90ad 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-connectors'] --- import kbnSearchConnectorsObj from './kbn_search_connectors.devdocs.json'; diff --git a/api_docs/kbn_search_errors.mdx b/api_docs/kbn_search_errors.mdx index ce42067d7bbb3..7eaa6b747aa51 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-02-05 +date: 2024-02-07 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 ef3f069558392..81dd1e0727bf8 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-02-05 +date: 2024-02-07 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 4affbc620650e..d1a8b19013b13 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-response-warnings'] --- import kbnSearchResponseWarningsObj from './kbn_search_response_warnings.devdocs.json'; diff --git a/api_docs/kbn_security_plugin_types_common.mdx b/api_docs/kbn_security_plugin_types_common.mdx index c160d8827cbb4..26578a20ada2a 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-02-05 +date: 2024-02-07 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 d01b28f9afc74..4cb43c5300c8a 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-02-05 +date: 2024-02-07 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 766705e251dec..4a16025702cda 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-02-05 +date: 2024-02-07 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_features.mdx b/api_docs/kbn_security_solution_features.mdx index fa0158fb3cf5a..2114c9b358355 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-02-05 +date: 2024-02-07 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 33079370fb9ff..dccd3e8b2bc2f 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-02-05 +date: 2024-02-07 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 3421ecfa7c877..d5ab11ffa55e2 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-02-05 +date: 2024-02-07 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 9f1697ad96fa5..2e00413d1d20b 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-02-05 +date: 2024-02-07 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 7e2354735e66a..c5ed87981cbe5 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-02-05 +date: 2024-02-07 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 db18493c50b0e..053e4097ff49b 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-02-05 +date: 2024-02-07 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 35d8e7f3c598f..42d769bea843e 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-02-05 +date: 2024-02-07 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 ec112c294386e..fe8b7c70ee53b 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-es-utils'] --- import kbnSecuritysolutionEsUtilsObj from './kbn_securitysolution_es_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_exception_list_components.mdx b/api_docs/kbn_securitysolution_exception_list_components.mdx index 6713cdef9ee35..947265540f56c 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-exception-list-components'] --- import kbnSecuritysolutionExceptionListComponentsObj from './kbn_securitysolution_exception_list_components.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_grouping.mdx b/api_docs/kbn_securitysolution_grouping.mdx index cc5139d59fdcf..9ec6d33ef6026 100644 --- a/api_docs/kbn_securitysolution_grouping.mdx +++ b/api_docs/kbn_securitysolution_grouping.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-grouping title: "@kbn/securitysolution-grouping" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-grouping plugin -date: 2024-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-grouping'] --- import kbnSecuritysolutionGroupingObj from './kbn_securitysolution_grouping.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_hook_utils.mdx b/api_docs/kbn_securitysolution_hook_utils.mdx index b406728b023a4..90c6011c58e2f 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-hook-utils'] --- import kbnSecuritysolutionHookUtilsObj from './kbn_securitysolution_hook_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx b/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx index deb73eb28c3ad..a20b15ca253a0 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-02-05 +date: 2024-02-07 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 687af293090a1..76787f54b5f51 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-02-05 +date: 2024-02-07 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 b1f194bdaac1a..7de1ceeb67481 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-02-05 +date: 2024-02-07 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 ea292c439b63a..fb562facd22b3 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-02-05 +date: 2024-02-07 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 86f1c89ccddc3..3fc3a0250b1ef 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-02-05 +date: 2024-02-07 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 f735ff2e5b6ce..1ad56da232f84 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-02-05 +date: 2024-02-07 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 b089e1c588a9a..35a6c48669d6e 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-02-05 +date: 2024-02-07 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 ee832f01ad89a..154eb52d04636 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-utils'] --- import kbnSecuritysolutionListUtilsObj from './kbn_securitysolution_list_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_rules.mdx b/api_docs/kbn_securitysolution_rules.mdx index 046fd85fad669..bf953c9cac540 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-02-05 +date: 2024-02-07 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 8c6e45cf67bd3..b45c9209aa460 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-t-grid'] --- import kbnSecuritysolutionTGridObj from './kbn_securitysolution_t_grid.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_utils.mdx b/api_docs/kbn_securitysolution_utils.mdx index b364c39376108..cd737dcbce579 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-utils'] --- import kbnSecuritysolutionUtilsObj from './kbn_securitysolution_utils.devdocs.json'; diff --git a/api_docs/kbn_server_http_tools.mdx b/api_docs/kbn_server_http_tools.mdx index bf5639c087086..e7a8d5fda8264 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-02-05 +date: 2024-02-07 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 1fb2abd893413..9d1746430fd3f 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/server-route-repository'] --- import kbnServerRouteRepositoryObj from './kbn_server_route_repository.devdocs.json'; diff --git a/api_docs/kbn_serverless_common_settings.mdx b/api_docs/kbn_serverless_common_settings.mdx index 50840a858c9c3..a2ea62bf8d8fa 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-02-05 +date: 2024-02-07 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 0e2f4154c73d4..fd5b569a35d7a 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-02-05 +date: 2024-02-07 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 f76daa0799b6c..e45f587b33064 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-02-05 +date: 2024-02-07 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 41fd6e762d44e..7f941b99fd3cf 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-02-05 +date: 2024-02-07 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 960730bdc84f0..d8ed16116d305 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-02-05 +date: 2024-02-07 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 87ee3c1479213..2cd19490dc417 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-02-05 +date: 2024-02-07 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 0e20db8ecb8ce..a8621f5cfb8f7 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-02-05 +date: 2024-02-07 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 793b8a1d174c4..84595af506bcc 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-02-05 +date: 2024-02-07 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 4a327e7ec6760..fc44947498518 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-02-05 +date: 2024-02-07 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 e304a71ca241d..bbd7421273718 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-02-05 +date: 2024-02-07 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 3159c5425faf6..aaf383f2235d1 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-02-05 +date: 2024-02-07 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 7cd3572e57c82..a2eea7464b583 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-card-no-data-mocks'] --- import kbnSharedUxCardNoDataMocksObj from './kbn_shared_ux_card_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_chrome_navigation.mdx b/api_docs/kbn_shared_ux_chrome_navigation.mdx index d822131e2b712..2b5147e127d02 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-chrome-navigation'] --- import kbnSharedUxChromeNavigationObj from './kbn_shared_ux_chrome_navigation.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_error_boundary.mdx b/api_docs/kbn_shared_ux_error_boundary.mdx index 9042e08952948..bf9ba0a0a3664 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-02-05 +date: 2024-02-07 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 6248d6f9d3655..f3945a4f9a574 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-02-05 +date: 2024-02-07 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 25a1f68ea5acd..dfc1c7ce08e5b 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-02-05 +date: 2024-02-07 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 36f5d8de23f2b..5409cc9b1feae 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-02-05 +date: 2024-02-07 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 66dae6568a47b..89023f1950880 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-02-05 +date: 2024-02-07 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 3ae4858785151..7017e8b730be7 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-02-05 +date: 2024-02-07 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 09cba02bd3a9f..a0a1c1c5401e4 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-02-05 +date: 2024-02-07 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 d32ff91404ecd..389eac8a318c8 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-02-05 +date: 2024-02-07 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 c430ef9793293..2de007264f074 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-02-05 +date: 2024-02-07 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 fff04e9aeb6d0..e2c7899816306 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-02-05 +date: 2024-02-07 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 6859779a1328e..9614c2d9ec727 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-02-05 +date: 2024-02-07 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 c0d297a05c826..d429245466b1b 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-02-05 +date: 2024-02-07 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 e2f1477091d11..23cf080050d2c 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-02-05 +date: 2024-02-07 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.devdocs.json b/api_docs/kbn_shared_ux_page_analytics_no_data.devdocs.json index cf6703bbdd7bb..fb96ab1a21727 100644 --- a/api_docs/kbn_shared_ux_page_analytics_no_data.devdocs.json +++ b/api_docs/kbn_shared_ux_page_analytics_no_data.devdocs.json @@ -13,7 +13,7 @@ "\nAn entire page that can be displayed when Kibana \"has no data\", specifically for Analytics. Uses\nservices from a Provider to supply props to a pure component." ], "signature": [ - "({ onDataViewCreated, allowAdHocDataView, }: ", + "({ onDataViewCreated, onESQLNavigationComplete, allowAdHocDataView, }: ", "AnalyticsNoDataPageProps", ") => JSX.Element" ], @@ -26,7 +26,7 @@ "id": "def-public.AnalyticsNoDataPage.$1", "type": "Object", "tags": [], - "label": "{\n onDataViewCreated,\n allowAdHocDataView,\n}", + "label": "{\n onDataViewCreated,\n onESQLNavigationComplete,\n allowAdHocDataView,\n}", "description": [], "signature": [ "AnalyticsNoDataPageProps" @@ -50,7 +50,7 @@ "\nA pure component of an entire page that can be displayed when Kibana \"has no data\", specifically for Analytics." ], "signature": [ - "({ onDataViewCreated, allowAdHocDataView, showPlainSpinner, ...services }: React.PropsWithChildren) => JSX.Element" + "({ onDataViewCreated, onESQLNavigationComplete, allowAdHocDataView, showPlainSpinner, ...services }: React.PropsWithChildren) => JSX.Element" ], "path": "packages/shared-ux/page/analytics_no_data/impl/src/analytics_no_data_page.component.tsx", "deprecated": false, @@ -61,7 +61,7 @@ "id": "def-public.AnalyticsNoDataPage.$1", "type": "CompoundType", "tags": [], - "label": "{\n onDataViewCreated,\n allowAdHocDataView,\n showPlainSpinner,\n ...services\n}", + "label": "{\n onDataViewCreated,\n onESQLNavigationComplete,\n allowAdHocDataView,\n showPlainSpinner,\n ...services\n}", "description": [], "signature": [ "React.PropsWithChildren" @@ -280,6 +280,24 @@ "path": "packages/shared-ux/page/analytics_no_data/types/index.d.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "@kbn/shared-ux-page-analytics-no-data", + "id": "def-public.AnalyticsNoDataPageProps.onESQLNavigationComplete", + "type": "Function", + "tags": [], + "label": "onESQLNavigationComplete", + "description": [ + "Handler for when try ES|QL is clicked and user has been navigated to try ES|QL in discover." + ], + "signature": [ + "(() => void) | undefined" + ], + "path": "packages/shared-ux/page/analytics_no_data/types/index.d.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] } ], "initialIsOpen": false 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 34e394dcedebb..5754df2b1c929 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-analytics-no-data'] --- import kbnSharedUxPageAnalyticsNoDataObj from './kbn_shared_ux_page_analytics_no_data.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 | |-------------------|-----------|------------------------|-----------------| -| 18 | 0 | 9 | 1 | +| 19 | 0 | 9 | 1 | ## Client 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 2778fe43ba1d0..037c5534676af 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-02-05 +date: 2024-02-07 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.devdocs.json b/api_docs/kbn_shared_ux_page_kibana_no_data.devdocs.json index 7a9362e266763..9d83d0442552c 100644 --- a/api_docs/kbn_shared_ux_page_kibana_no_data.devdocs.json +++ b/api_docs/kbn_shared_ux_page_kibana_no_data.devdocs.json @@ -13,7 +13,7 @@ "\nA page to display when Kibana has no data, prompting a person to add integrations or create a new data view." ], "signature": [ - "({ onDataViewCreated, noDataConfig, allowAdHocDataView, showPlainSpinner, }: ", + "({ onDataViewCreated, onESQLNavigationComplete, noDataConfig, allowAdHocDataView, showPlainSpinner, }: ", "KibanaNoDataPageProps", ") => JSX.Element | null" ], @@ -26,7 +26,7 @@ "id": "def-public.KibanaNoDataPage.$1", "type": "Object", "tags": [], - "label": "{\n onDataViewCreated,\n noDataConfig,\n allowAdHocDataView,\n showPlainSpinner,\n}", + "label": "{\n onDataViewCreated,\n onESQLNavigationComplete,\n noDataConfig,\n allowAdHocDataView,\n showPlainSpinner,\n}", "description": [], "signature": [ "KibanaNoDataPageProps" 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 56e4da86cbbc0..65c22c57b12c8 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-02-05 +date: 2024-02-07 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 d4a056d4bd05b..7cff5aa8ac6c2 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-02-05 +date: 2024-02-07 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 c025a28e083b7..2f92c76ede634 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-02-05 +date: 2024-02-07 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 4d9fa62723f6a..c03aa44c12731 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-02-05 +date: 2024-02-07 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 c5848bfb7a337..44b448913fc0c 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-02-05 +date: 2024-02-07 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 d3a6be09a35a5..ca2aa34d8d303 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-02-05 +date: 2024-02-07 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 34cc0c20aaffc..7b49d2bfa155e 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-02-05 +date: 2024-02-07 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 2e144e0a35780..0c964dc868dfa 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-02-05 +date: 2024-02-07 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 5b368de1fad2f..3b98470178406 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-02-05 +date: 2024-02-07 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.devdocs.json b/api_docs/kbn_shared_ux_prompt_no_data_views.devdocs.json index b45b0685c9896..cc154eb46469f 100644 --- a/api_docs/kbn_shared_ux_prompt_no_data_views.devdocs.json +++ b/api_docs/kbn_shared_ux_prompt_no_data_views.devdocs.json @@ -30,7 +30,7 @@ "\nA service-enabled component that provides Kibana-specific functionality to the `NoDataViewsPrompt`\ncomponent.\n\nUse of this component requires both the `EuiTheme` context as well as a `NoDataViewsPrompt` provider." ], "signature": [ - "({ onDataViewCreated, allowAdHocDataView, }: ", + "({ onDataViewCreated, onESQLNavigationComplete, allowAdHocDataView, }: ", "NoDataViewsPromptProps", ") => JSX.Element" ], @@ -43,7 +43,7 @@ "id": "def-public.NoDataViewsPrompt.$1", "type": "Object", "tags": [], - "label": "{\n onDataViewCreated,\n allowAdHocDataView = false,\n}", + "label": "{\n onDataViewCreated,\n onESQLNavigationComplete,\n allowAdHocDataView = false,\n}", "description": [], "signature": [ "NoDataViewsPromptProps" @@ -67,7 +67,7 @@ "\nA presentational component that is shown in cases when there are no data views created yet." ], "signature": [ - "({ onClickCreate, canCreateNewDataView, dataViewsDocLink, emptyPromptColor, }: ", + "({ onClickCreate, canCreateNewDataView, dataViewsDocLink, onTryESQL, esqlDocLink, emptyPromptColor, }: ", "NoDataViewsPromptComponentProps", ") => JSX.Element" ], @@ -80,7 +80,7 @@ "id": "def-public.NoDataViewsPrompt.$1", "type": "Object", "tags": [], - "label": "{\n onClickCreate,\n canCreateNewDataView,\n dataViewsDocLink,\n emptyPromptColor = 'plain',\n}", + "label": "{\n onClickCreate,\n canCreateNewDataView,\n dataViewsDocLink,\n onTryESQL,\n esqlDocLink,\n emptyPromptColor = 'plain',\n}", "description": [], "signature": [ "NoDataViewsPromptComponentProps" @@ -171,6 +171,41 @@ ], "returnComment": [], "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/shared-ux-prompt-no-data-views", + "id": "def-public.useOnTryESQL", + "type": "Function", + "tags": [], + "label": "useOnTryESQL", + "description": [], + "signature": [ + "({ locatorClient, navigateToApp }: ", + "UseOnTryEsqlParams", + ") => (() => void) | undefined" + ], + "path": "packages/shared-ux/prompt/no_data_views/impl/src/hooks/use_on_try_esql.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/shared-ux-prompt-no-data-views", + "id": "def-public.useOnTryESQL.$1", + "type": "Object", + "tags": [], + "label": "{ locatorClient, navigateToApp }", + "description": [], + "signature": [ + "UseOnTryEsqlParams" + ], + "path": "packages/shared-ux/prompt/no_data_views/impl/src/hooks/use_on_try_esql.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false } ], "interfaces": [ @@ -198,6 +233,38 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "@kbn/shared-ux-prompt-no-data-views", + "id": "def-public.NoDataViewsPromptComponentProps.dataViewsDocLink", + "type": "string", + "tags": [], + "label": "dataViewsDocLink", + "description": [ + "Link to documentation on data views." + ], + "signature": [ + "string | undefined" + ], + "path": "packages/shared-ux/prompt/no_data_views/types/index.d.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/shared-ux-prompt-no-data-views", + "id": "def-public.NoDataViewsPromptComponentProps.emptyPromptColor", + "type": "CompoundType", + "tags": [], + "label": "emptyPromptColor", + "description": [ + "The background color of the prompt; defaults to `plain`." + ], + "signature": [ + "\"warning\" | \"success\" | \"subdued\" | \"primary\" | \"accent\" | \"danger\" | \"plain\" | \"transparent\" | undefined" + ], + "path": "packages/shared-ux/prompt/no_data_views/types/index.d.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "@kbn/shared-ux-prompt-no-data-views", "id": "def-public.NoDataViewsPromptComponentProps.onClickCreate", @@ -218,31 +285,33 @@ }, { "parentPluginId": "@kbn/shared-ux-prompt-no-data-views", - "id": "def-public.NoDataViewsPromptComponentProps.dataViewsDocLink", - "type": "string", + "id": "def-public.NoDataViewsPromptComponentProps.onTryESQL", + "type": "Function", "tags": [], - "label": "dataViewsDocLink", + "label": "onTryESQL", "description": [ - "Link to documentation on data views." + "Handler for someone wanting to try ES|QL." ], "signature": [ - "string | undefined" + "(() => void) | undefined" ], "path": "packages/shared-ux/prompt/no_data_views/types/index.d.ts", "deprecated": false, - "trackAdoption": false + "trackAdoption": false, + "children": [], + "returnComment": [] }, { "parentPluginId": "@kbn/shared-ux-prompt-no-data-views", - "id": "def-public.NoDataViewsPromptComponentProps.emptyPromptColor", - "type": "CompoundType", + "id": "def-public.NoDataViewsPromptComponentProps.esqlDocLink", + "type": "string", "tags": [], - "label": "emptyPromptColor", + "label": "esqlDocLink", "description": [ - "The background color of the prompt; defaults to `plain`." + "Link to documentation on ES|QL." ], "signature": [ - "\"warning\" | \"success\" | \"subdued\" | \"primary\" | \"accent\" | \"danger\" | \"plain\" | \"transparent\" | undefined" + "string | undefined" ], "path": "packages/shared-ux/prompt/no_data_views/types/index.d.ts", "deprecated": false, @@ -272,7 +341,9 @@ "label": "coreStart", "description": [], "signature": [ - "{ docLinks: { links: { indexPatterns: { introduction: string; }; }; }; }" + "{ docLinks: { links: { indexPatterns: { introduction: string; }; query: { queryESQL: string; }; }; }; application: { navigateToApp: ", + "NavigateToAppFn", + "; }; }" ], "path": "packages/shared-ux/prompt/no_data_views/types/index.d.ts", "deprecated": false, @@ -293,6 +364,22 @@ "path": "packages/shared-ux/prompt/no_data_views/types/index.d.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "@kbn/shared-ux-prompt-no-data-views", + "id": "def-public.NoDataViewsPromptKibanaDependencies.share", + "type": "Object", + "tags": [], + "label": "share", + "description": [], + "signature": [ + "{ url: { locators: ", + "ILocatorClient", + "; }; } | undefined" + ], + "path": "packages/shared-ux/prompt/no_data_views/types/index.d.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -308,6 +395,22 @@ "deprecated": false, "trackAdoption": false, "children": [ + { + "parentPluginId": "@kbn/shared-ux-prompt-no-data-views", + "id": "def-public.NoDataViewsPromptProps.allowAdHocDataView", + "type": "CompoundType", + "tags": [], + "label": "allowAdHocDataView", + "description": [ + "if set to true allows creation of an ad-hoc data view from data view editor" + ], + "signature": [ + "boolean | undefined" + ], + "path": "packages/shared-ux/prompt/no_data_views/types/index.d.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "@kbn/shared-ux-prompt-no-data-views", "id": "def-public.NoDataViewsPromptProps.onDataViewCreated", @@ -344,19 +447,21 @@ }, { "parentPluginId": "@kbn/shared-ux-prompt-no-data-views", - "id": "def-public.NoDataViewsPromptProps.allowAdHocDataView", - "type": "CompoundType", + "id": "def-public.NoDataViewsPromptProps.onESQLNavigationComplete", + "type": "Function", "tags": [], - "label": "allowAdHocDataView", + "label": "onESQLNavigationComplete", "description": [ - "if set to true allows creation of an ad-hoc data view from data view editor" + "Handler for when try ES|QL is clicked and user has been navigated to try ES|QL in discover." ], "signature": [ - "boolean | undefined" + "(() => void) | undefined" ], "path": "packages/shared-ux/prompt/no_data_views/types/index.d.ts", "deprecated": false, - "trackAdoption": false + "trackAdoption": false, + "children": [], + "returnComment": [] } ], "initialIsOpen": false @@ -435,6 +540,35 @@ "path": "packages/shared-ux/prompt/no_data_views/types/index.d.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "@kbn/shared-ux-prompt-no-data-views", + "id": "def-public.NoDataViewsPromptServices.onTryESQL", + "type": "Function", + "tags": [], + "label": "onTryESQL", + "description": [ + "Get a handler for trying ES|QL" + ], + "signature": [ + "(() => void) | undefined" + ], + "path": "packages/shared-ux/prompt/no_data_views/types/index.d.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/shared-ux-prompt-no-data-views", + "id": "def-public.NoDataViewsPromptServices.esqlDocLink", + "type": "string", + "tags": [], + "label": "esqlDocLink", + "description": [ + "A link to the documentation for ES|QL" + ], + "path": "packages/shared-ux/prompt/no_data_views/types/index.d.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false 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 026f5a0dac776..294dff1106e56 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-prompt-no-data-views'] --- import kbnSharedUxPromptNoDataViewsObj from './kbn_shared_ux_prompt_no_data_views.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 | |-------------------|-----------|------------------------|-----------------| -| 26 | 0 | 11 | 0 | +| 34 | 0 | 14 | 1 | ## Client diff --git a/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.devdocs.json b/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.devdocs.json index 44e830b476844..c7f0d67b79443 100644 --- a/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.devdocs.json +++ b/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.devdocs.json @@ -143,6 +143,92 @@ ] } ] + }, + { + "parentPluginId": "@kbn/shared-ux-prompt-no-data-views-mocks", + "id": "def-public.StorybookMock.serviceArguments.esqlDocLink", + "type": "Object", + "tags": [], + "label": "esqlDocLink", + "description": [], + "path": "packages/shared-ux/prompt/no_data_views/mocks/src/storybook.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/shared-ux-prompt-no-data-views-mocks", + "id": "def-public.StorybookMock.serviceArguments.esqlDocLink.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "(string | undefined)[]" + ], + "path": "packages/shared-ux/prompt/no_data_views/mocks/src/storybook.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/shared-ux-prompt-no-data-views-mocks", + "id": "def-public.StorybookMock.serviceArguments.esqlDocLink.control", + "type": "Object", + "tags": [], + "label": "control", + "description": [], + "path": "packages/shared-ux/prompt/no_data_views/mocks/src/storybook.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/shared-ux-prompt-no-data-views-mocks", + "id": "def-public.StorybookMock.serviceArguments.esqlDocLink.control.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "path": "packages/shared-ux/prompt/no_data_views/mocks/src/storybook.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ] + }, + { + "parentPluginId": "@kbn/shared-ux-prompt-no-data-views-mocks", + "id": "def-public.StorybookMock.serviceArguments.canTryEsql", + "type": "Object", + "tags": [], + "label": "canTryEsql", + "description": [], + "path": "packages/shared-ux/prompt/no_data_views/mocks/src/storybook.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/shared-ux-prompt-no-data-views-mocks", + "id": "def-public.StorybookMock.serviceArguments.canTryEsql.control", + "type": "string", + "tags": [], + "label": "control", + "description": [], + "path": "packages/shared-ux/prompt/no_data_views/mocks/src/storybook.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/shared-ux-prompt-no-data-views-mocks", + "id": "def-public.StorybookMock.serviceArguments.canTryEsql.defaultValue", + "type": "boolean", + "tags": [], + "label": "defaultValue", + "description": [], + "path": "packages/shared-ux/prompt/no_data_views/mocks/src/storybook.ts", + "deprecated": false, + "trackAdoption": false + } + ] } ] }, @@ -282,7 +368,7 @@ "label": "Params", "description": [], "signature": [ - "{ canCreateNewDataView: any; dataViewsDocLink: any; }" + "{ canCreateNewDataView: any; dataViewsDocLink: any; esqlDocLink: any; canTryEsql: any; }" ], "path": "packages/shared-ux/prompt/no_data_views/mocks/src/storybook.ts", "deprecated": false, 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 e51f5269e3990..ff2135568798d 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-02-05 +date: 2024-02-07 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'; @@ -21,7 +21,7 @@ Contact [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sh | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 17 | 0 | 16 | 0 | +| 24 | 0 | 23 | 0 | ## Client diff --git a/api_docs/kbn_shared_ux_prompt_not_found.mdx b/api_docs/kbn_shared_ux_prompt_not_found.mdx index 26e45c2cecefc..abedb63711b05 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-02-05 +date: 2024-02-07 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 2a70adc4a8c0a..9cd99643b0ad3 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-02-05 +date: 2024-02-07 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 7e292a9b51041..54a3934ef59d3 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-02-05 +date: 2024-02-07 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 129774d7c27a1..72f3abd64678e 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-02-05 +date: 2024-02-07 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 8c99349366ce4..64487e494b695 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-storybook-mock'] --- import kbnSharedUxStorybookMockObj from './kbn_shared_ux_storybook_mock.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_utility.mdx b/api_docs/kbn_shared_ux_utility.mdx index 8acd92437f48b..58db85c5e810e 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-02-05 +date: 2024-02-07 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 2842169076891..bf6ddf27cd61f 100644 --- a/api_docs/kbn_slo_schema.devdocs.json +++ b/api_docs/kbn_slo_schema.devdocs.json @@ -773,6 +773,36 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/slo-schema", + "id": "def-common.FindSLOGroupsParams", + "type": "Type", + "tags": [], + "label": "FindSLOGroupsParams", + "description": [], + "signature": [ + "{ page?: string | undefined; perPage?: string | undefined; groupBy?: \"status\" | \"ungrouped\" | \"slo.tags\" | \"slo.indicator.type\" | undefined; kqlQuery?: string | undefined; filters?: string | undefined; }" + ], + "path": "x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/slo-schema", + "id": "def-common.FindSLOGroupsResponse", + "type": "Type", + "tags": [], + "label": "FindSLOGroupsResponse", + "description": [], + "signature": [ + "{ page: number; perPage: number; total: number; results: { group: string; groupBy: string; summary: { total: number; worst: { sliValue: number; status: string; slo: { id: string; instanceId: string; name: string; }; }; violated: number; healthy: number; degrading: number; noData: number; }; }[]; }" + ], + "path": "x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/slo-schema", "id": "def-common.FindSLOParams", @@ -901,6 +931,21 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/slo-schema", + "id": "def-common.GroupSummary", + "type": "Type", + "tags": [], + "label": "GroupSummary", + "description": [], + "signature": [ + "{ total: number; worst: { sliValue: number; status: string; slo: { id: string; instanceId: string; name: string; }; }; violated: number; healthy: number; degrading: number; noData: number; }" + ], + "path": "x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/slo-schema", "id": "def-common.HistogramIndicator", @@ -1051,6 +1096,21 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/slo-schema", + "id": "def-common.SLOGroupWithSummaryResponse", + "type": "Type", + "tags": [], + "label": "SLOGroupWithSummaryResponse", + "description": [], + "signature": [ + "{ group: string; groupBy: string; summary: { total: number; worst: { sliValue: number; status: string; slo: { id: string; instanceId: string; name: string; }; }; violated: number; healthy: number; degrading: number; noData: number; }; }" + ], + "path": "x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/slo-schema", "id": "def-common.SLOResponse", @@ -2627,6 +2687,98 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/slo-schema", + "id": "def-common.findSLOGroupsParamsSchema", + "type": "Object", + "tags": [], + "label": "findSLOGroupsParamsSchema", + "description": [], + "signature": [ + "PartialC", + "<{ query: ", + "PartialC", + "<{ page: ", + "StringC", + "; perPage: ", + "StringC", + "; groupBy: ", + "UnionC", + "<[", + "LiteralC", + "<\"ungrouped\">, ", + "LiteralC", + "<\"slo.tags\">, ", + "LiteralC", + "<\"status\">, ", + "LiteralC", + "<\"slo.indicator.type\">]>; kqlQuery: ", + "StringC", + "; filters: ", + "StringC", + "; }>; }>" + ], + "path": "x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/slo-schema", + "id": "def-common.findSLOGroupsResponseSchema", + "type": "Object", + "tags": [], + "label": "findSLOGroupsResponseSchema", + "description": [], + "signature": [ + "TypeC", + "<{ page: ", + "NumberC", + "; perPage: ", + "NumberC", + "; total: ", + "NumberC", + "; results: ", + "ArrayC", + "<", + "TypeC", + "<{ group: ", + "StringC", + "; groupBy: ", + "StringC", + "; summary: ", + "TypeC", + "<{ total: ", + "NumberC", + "; worst: ", + "TypeC", + "<{ sliValue: ", + "NumberC", + "; status: ", + "StringC", + "; slo: ", + "TypeC", + "<{ id: ", + "StringC", + "; instanceId: ", + "StringC", + "; name: ", + "StringC", + "; }>; }>; violated: ", + "NumberC", + "; healthy: ", + "NumberC", + "; degrading: ", + "NumberC", + "; noData: ", + "NumberC", + "; }>; }>>; }>" + ], + "path": "x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/slo-schema", "id": "def-common.findSLOParamsSchema", @@ -4211,6 +4363,46 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/slo-schema", + "id": "def-common.groupSummarySchema", + "type": "Object", + "tags": [], + "label": "groupSummarySchema", + "description": [], + "signature": [ + "TypeC", + "<{ total: ", + "NumberC", + "; worst: ", + "TypeC", + "<{ sliValue: ", + "NumberC", + "; status: ", + "StringC", + "; slo: ", + "TypeC", + "<{ id: ", + "StringC", + "; instanceId: ", + "StringC", + "; name: ", + "StringC", + "; }>; }>; violated: ", + "NumberC", + "; healthy: ", + "NumberC", + "; degrading: ", + "NumberC", + "; noData: ", + "NumberC", + "; }>" + ], + "path": "x-pack/packages/kbn-slo-schema/src/schema/common.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/slo-schema", "id": "def-common.histogramIndicatorSchema", @@ -5691,6 +5883,52 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/slo-schema", + "id": "def-common.sloGroupWithSummaryResponseSchema", + "type": "Object", + "tags": [], + "label": "sloGroupWithSummaryResponseSchema", + "description": [], + "signature": [ + "TypeC", + "<{ group: ", + "StringC", + "; groupBy: ", + "StringC", + "; summary: ", + "TypeC", + "<{ total: ", + "NumberC", + "; worst: ", + "TypeC", + "<{ sliValue: ", + "NumberC", + "; status: ", + "StringC", + "; slo: ", + "TypeC", + "<{ id: ", + "StringC", + "; instanceId: ", + "StringC", + "; name: ", + "StringC", + "; }>; }>; violated: ", + "NumberC", + "; healthy: ", + "NumberC", + "; degrading: ", + "NumberC", + "; noData: ", + "NumberC", + "; }>; }>" + ], + "path": "x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/slo-schema", "id": "def-common.sloIdSchema", diff --git a/api_docs/kbn_slo_schema.mdx b/api_docs/kbn_slo_schema.mdx index 87f6d2d5f1a8a..0fffc0c9814c6 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/slo-schema'] --- import kbnSloSchemaObj from './kbn_slo_schema.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 | |-------------------|-----------|------------------------|-----------------| -| 146 | 0 | 146 | 0 | +| 154 | 0 | 154 | 0 | ## Common diff --git a/api_docs/kbn_some_dev_log.mdx b/api_docs/kbn_some_dev_log.mdx index 6b62fdb36a6cd..18d8ef1266998 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-02-05 +date: 2024-02-07 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 170dd855f1d51..ebd8ff3489b11 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-02-05 +date: 2024-02-07 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 8a1dca67c96eb..bfd29dc3cd15c 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-02-05 +date: 2024-02-07 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 d365a5e134de7..9e64640e19288 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-02-05 +date: 2024-02-07 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 fa2c216c98b13..1e4a273d61800 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/storybook'] --- import kbnStorybookObj from './kbn_storybook.devdocs.json'; diff --git a/api_docs/kbn_telemetry_tools.mdx b/api_docs/kbn_telemetry_tools.mdx index 53510f17825dc..5145a4afe06ca 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/telemetry-tools'] --- import kbnTelemetryToolsObj from './kbn_telemetry_tools.devdocs.json'; diff --git a/api_docs/kbn_test.mdx b/api_docs/kbn_test.mdx index 60fb864f4cb54..f5cd10097d9a1 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test'] --- import kbnTestObj from './kbn_test.devdocs.json'; diff --git a/api_docs/kbn_test_eui_helpers.mdx b/api_docs/kbn_test_eui_helpers.mdx index 0b76ef1aef88f..04687f3194fb1 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-02-05 +date: 2024-02-07 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 e43d09877f4ea..53df9442b2958 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-02-05 +date: 2024-02-07 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 5415e26f53308..450a98bdd3cf2 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-02-05 +date: 2024-02-07 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 593be947f90f7..8e1737f376a91 100644 --- a/api_docs/kbn_text_based_editor.devdocs.json +++ b/api_docs/kbn_text_based_editor.devdocs.json @@ -43,6 +43,14 @@ "section": "def-common.TimeRange", "text": "TimeRange" }, + " | undefined, dataView: ", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataView", + "text": "DataView" + }, " | undefined) => Promise<", { "pluginId": "expressions", @@ -128,6 +136,28 @@ "deprecated": false, "trackAdoption": false, "isRequired": false + }, + { + "parentPluginId": "@kbn/text-based-editor", + "id": "def-public.fetchFieldsFromESQL.$4", + "type": "Object", + "tags": [], + "label": "dataView", + "description": [], + "signature": [ + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataView", + "text": "DataView" + }, + " | undefined" + ], + "path": "packages/kbn-text-based-editor/src/fetch_fields_from_esql.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false } ], "returnComment": [], diff --git a/api_docs/kbn_text_based_editor.mdx b/api_docs/kbn_text_based_editor.mdx index e80040095d8c2..7128842bcddc3 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/text-based-editor'] --- import kbnTextBasedEditorObj from './kbn_text_based_editor.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/k | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 26 | 0 | 10 | 0 | +| 27 | 0 | 11 | 0 | ## Client diff --git a/api_docs/kbn_tooling_log.mdx b/api_docs/kbn_tooling_log.mdx index ee0e514f8bc2f..cd004a4ffae0e 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-02-05 +date: 2024-02-07 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 8136ad8cc11b8..e6650effde67f 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-02-05 +date: 2024-02-07 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_ts_projects.mdx b/api_docs/kbn_ts_projects.mdx index 7f152b686cf66..e495faad72c9f 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-02-05 +date: 2024-02-07 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 82a3a0cc338bf..e81492896807a 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-02-05 +date: 2024-02-07 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.devdocs.json b/api_docs/kbn_ui_actions_browser.devdocs.json index aabb32029e0d6..3e2991f568577 100644 --- a/api_docs/kbn_ui_actions_browser.devdocs.json +++ b/api_docs/kbn_ui_actions_browser.devdocs.json @@ -324,56 +324,6 @@ ], "initialIsOpen": false }, - { - "parentPluginId": "@kbn/ui-actions-browser", - "id": "def-common.RowClickContext", - "type": "Interface", - "tags": [], - "label": "RowClickContext", - "description": [], - "path": "packages/kbn-ui-actions-browser/src/triggers/row_click_trigger.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "@kbn/ui-actions-browser", - "id": "def-common.RowClickContext.embeddable", - "type": "Unknown", - "tags": [], - "label": "embeddable", - "description": [], - "signature": [ - "unknown" - ], - "path": "packages/kbn-ui-actions-browser/src/triggers/row_click_trigger.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "@kbn/ui-actions-browser", - "id": "def-common.RowClickContext.data", - "type": "Object", - "tags": [], - "label": "data", - "description": [], - "signature": [ - "{ rowIndex: number; table: ", - { - "pluginId": "expressions", - "scope": "common", - "docId": "kibExpressionsPluginApi", - "section": "def-common.Datatable", - "text": "Datatable" - }, - "; columns?: string[] | undefined; }" - ], - "path": "packages/kbn-ui-actions-browser/src/triggers/row_click_trigger.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, { "parentPluginId": "@kbn/ui-actions-browser", "id": "def-common.Trigger", @@ -475,6 +425,37 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/ui-actions-browser", + "id": "def-common.RowClickContext", + "type": "Type", + "tags": [], + "label": "RowClickContext", + "description": [], + "signature": [ + "Partial<", + { + "pluginId": "@kbn/presentation-publishing", + "scope": "common", + "docId": "kibKbnPresentationPublishingPluginApi", + "section": "def-common.EmbeddableApiContext", + "text": "EmbeddableApiContext" + }, + "> & { data: { rowIndex: number; table: ", + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.Datatable", + "text": "Datatable" + }, + "; columns?: string[] | undefined; }; }" + ], + "path": "packages/kbn-ui-actions-browser/src/triggers/row_click_trigger.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/ui-actions-browser", "id": "def-common.VISUALIZE_FIELD_TRIGGER", diff --git a/api_docs/kbn_ui_actions_browser.mdx b/api_docs/kbn_ui_actions_browser.mdx index 0b0be11ed048e..a233dedd0ea33 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ui-actions-browser'] --- import kbnUiActionsBrowserObj from './kbn_ui_actions_browser.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 | |-------------------|-----------|------------------------|-----------------| -| 44 | 0 | 30 | 0 | +| 42 | 0 | 28 | 0 | ## Common diff --git a/api_docs/kbn_ui_shared_deps_src.mdx b/api_docs/kbn_ui_shared_deps_src.mdx index cdf477f221753..1cdea5c282b87 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-02-05 +date: 2024-02-07 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 45167bedaab44..5cec48c951d73 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-02-05 +date: 2024-02-07 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.mdx b/api_docs/kbn_unified_data_table.mdx index eb16539c4681b..65d7a7dffcffb 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/unified-data-table'] --- import kbnUnifiedDataTableObj from './kbn_unified_data_table.devdocs.json'; diff --git a/api_docs/kbn_unified_doc_viewer.mdx b/api_docs/kbn_unified_doc_viewer.mdx index 0b36748a628f7..af6d742fbbb6d 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-02-05 +date: 2024-02-07 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.mdx b/api_docs/kbn_unified_field_list.mdx index b843a24b2054e..29cf70e2bcb56 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-02-05 +date: 2024-02-07 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 057cc182a4879..fc3bbc2c287d0 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/unsaved-changes-badge'] --- import kbnUnsavedChangesBadgeObj from './kbn_unsaved_changes_badge.devdocs.json'; diff --git a/api_docs/kbn_use_tracked_promise.mdx b/api_docs/kbn_use_tracked_promise.mdx index 8e6d18d6d684c..2437fad6cddae 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-02-05 +date: 2024-02-07 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 7789f32b0a6a4..9b3f4b02f8afb 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-02-05 +date: 2024-02-07 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 8e3f1d0e2b069..fb19a2483463e 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-02-05 +date: 2024-02-07 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 6f7e6188f26a4..b88380e4f7779 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-02-05 +date: 2024-02-07 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 11ba59baede2a..8b23ecd6fb0ba 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-02-05 +date: 2024-02-07 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 47f2d7bf1398f..ee10e9585752b 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-02-05 +date: 2024-02-07 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.devdocs.json b/api_docs/kbn_visualization_utils.devdocs.json index 649bfd18f772a..b52439e9940d1 100644 --- a/api_docs/kbn_visualization_utils.devdocs.json +++ b/api_docs/kbn_visualization_utils.devdocs.json @@ -19,6 +19,188 @@ "common": { "classes": [], "functions": [ + { + "parentPluginId": "@kbn/visualization-utils", + "id": "def-common.getLensAttributesFromSuggestion", + "type": "Function", + "tags": [], + "label": "getLensAttributesFromSuggestion", + "description": [], + "signature": [ + "({ filters, query, suggestion, dataView, }: { filters: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "[]; query: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Query", + "text": "Query" + }, + " | ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.AggregateQuery", + "text": "AggregateQuery" + }, + "; suggestion: ", + "Suggestion", + " | undefined; dataView?: ", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataView", + "text": "DataView" + }, + " | undefined; }) => { title: string; references: { id: string; name: string; type: string; }[]; state: { adHocDataViews?: { [x: string]: ", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataViewSpec", + "text": "DataViewSpec" + }, + "; } | undefined; datasourceStates: { [x: string]: {}; formBased?: undefined; } | { formBased: {}; }; filters: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "[]; query: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Query", + "text": "Query" + }, + " | ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.AggregateQuery", + "text": "AggregateQuery" + }, + "; visualization: {}; }; visualizationType: string; }" + ], + "path": "packages/kbn-visualization-utils/src/get_lens_attributes.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/visualization-utils", + "id": "def-common.getLensAttributesFromSuggestion.$1", + "type": "Object", + "tags": [], + "label": "{\n filters,\n query,\n suggestion,\n dataView,\n}", + "description": [], + "path": "packages/kbn-visualization-utils/src/get_lens_attributes.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/visualization-utils", + "id": "def-common.getLensAttributesFromSuggestion.$1.filters", + "type": "Array", + "tags": [], + "label": "filters", + "description": [], + "signature": [ + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "[]" + ], + "path": "packages/kbn-visualization-utils/src/get_lens_attributes.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/visualization-utils", + "id": "def-common.getLensAttributesFromSuggestion.$1.query", + "type": "CompoundType", + "tags": [], + "label": "query", + "description": [], + "signature": [ + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Query", + "text": "Query" + }, + " | ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.AggregateQuery", + "text": "AggregateQuery" + } + ], + "path": "packages/kbn-visualization-utils/src/get_lens_attributes.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/visualization-utils", + "id": "def-common.getLensAttributesFromSuggestion.$1.suggestion", + "type": "Object", + "tags": [], + "label": "suggestion", + "description": [], + "signature": [ + "Suggestion", + " | undefined" + ], + "path": "packages/kbn-visualization-utils/src/get_lens_attributes.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/visualization-utils", + "id": "def-common.getLensAttributesFromSuggestion.$1.dataView", + "type": "Object", + "tags": [], + "label": "dataView", + "description": [], + "signature": [ + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataView", + "text": "DataView" + }, + " | undefined" + ], + "path": "packages/kbn-visualization-utils/src/get_lens_attributes.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/visualization-utils", "id": "def-common.getTimeZone", diff --git a/api_docs/kbn_visualization_utils.mdx b/api_docs/kbn_visualization_utils.mdx index b2ad2ce8b607f..0b925c4d868ca 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/visualization-utils'] --- import kbnVisualizationUtilsObj from './kbn_visualization_utils.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 | |-------------------|-----------|------------------------|-----------------| -| 4 | 0 | 3 | 0 | +| 10 | 0 | 9 | 1 | ## Common diff --git a/api_docs/kbn_xstate_utils.mdx b/api_docs/kbn_xstate_utils.mdx index 3c3ed3087ef69..e12fe7d8b0db4 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-02-05 +date: 2024-02-07 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 50d1f11f8456f..eaaa54243e242 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-02-05 +date: 2024-02-07 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_helpers.mdx b/api_docs/kbn_zod_helpers.mdx index 0446657044304..bae132683b884 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-02-05 +date: 2024-02-07 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 0352d8c730c51..eb1820b3150f3 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaOverview'] --- import kibanaOverviewObj from './kibana_overview.devdocs.json'; diff --git a/api_docs/kibana_react.devdocs.json b/api_docs/kibana_react.devdocs.json index 047ff42231895..2dbf5cea1cffe 100644 --- a/api_docs/kibana_react.devdocs.json +++ b/api_docs/kibana_react.devdocs.json @@ -3314,7 +3314,7 @@ "label": "UrlTemplateEditor", "description": [], "signature": [ - "({ height, value, variables, onChange, placeholder, onEditor, Editor, }: React.PropsWithChildren<", + "({ height, fitToContent, value, variables, onChange, placeholder, onEditor, Editor, }: React.PropsWithChildren<", { "pluginId": "kibanaReact", "scope": "public", @@ -3333,7 +3333,7 @@ "id": "def-public.UrlTemplateEditor.$1", "type": "CompoundType", "tags": [], - "label": "{\n height = 105,\n value,\n variables,\n onChange,\n placeholder,\n onEditor,\n Editor = CodeEditor,\n}", + "label": "{\n height = 105,\n fitToContent,\n value,\n variables,\n onChange,\n placeholder,\n onEditor,\n Editor = CodeEditor,\n}", "description": [], "signature": [ "React.PropsWithChildren<", @@ -4532,6 +4532,20 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "kibanaReact", + "id": "def-public.UrlTemplateEditorProps.fitToContent", + "type": "Object", + "tags": [], + "label": "fitToContent", + "description": [], + "signature": [ + "{ minLines?: number | undefined; maxLines?: number | undefined; } | undefined" + ], + "path": "src/plugins/kibana_react/public/url_template_editor/url_template_editor.tsx", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "kibanaReact", "id": "def-public.UrlTemplateEditorProps.variables", diff --git a/api_docs/kibana_react.mdx b/api_docs/kibana_react.mdx index 4a28f6ea95df3..ad7821e034e2a 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaReact'] --- import kibanaReactObj from './kibana_react.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 | |-------------------|-----------|------------------------|-----------------| -| 152 | 0 | 120 | 3 | +| 153 | 0 | 121 | 3 | ## Client diff --git a/api_docs/kibana_utils.mdx b/api_docs/kibana_utils.mdx index d64864c18bfbd..f4535a9e9d3fa 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-02-05 +date: 2024-02-07 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 5868eba6dbcee..fd26049c8d620 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kubernetesSecurity'] --- import kubernetesSecurityObj from './kubernetes_security.devdocs.json'; diff --git a/api_docs/lens.devdocs.json b/api_docs/lens.devdocs.json index 09064262d23de..bcc08478559d3 100644 --- a/api_docs/lens.devdocs.json +++ b/api_docs/lens.devdocs.json @@ -3143,12 +3143,128 @@ "label": "onApply", "description": [], "signature": [ - "(() => void) | undefined" + "((newAttributes: LensAttributes<\"lnsXY\", ", + { + "pluginId": "lens", + "scope": "public", + "docId": "kibLensPluginApi", + "section": "def-public.XYState", + "text": "XYState" + }, + "> | LensAttributes<\"lnsPie\", ", + "PieVisualizationState", + "> | LensAttributes<\"lnsHeatmap\", ", + { + "pluginId": "lens", + "scope": "public", + "docId": "kibLensPluginApi", + "section": "def-public.HeatmapVisualizationState", + "text": "HeatmapVisualizationState" + }, + "> | LensAttributes<\"lnsGauge\", ", + { + "pluginId": "lens", + "scope": "public", + "docId": "kibLensPluginApi", + "section": "def-public.GaugeVisualizationState", + "text": "GaugeVisualizationState" + }, + "> | LensAttributes<\"lnsDatatable\", ", + { + "pluginId": "lens", + "scope": "public", + "docId": "kibLensPluginApi", + "section": "def-public.DatatableVisualizationState", + "text": "DatatableVisualizationState" + }, + "> | LensAttributes<\"lnsLegacyMetric\", ", + { + "pluginId": "lens", + "scope": "common", + "docId": "kibLensPluginApi", + "section": "def-common.LegacyMetricState", + "text": "LegacyMetricState" + }, + "> | LensAttributes<\"lnsMetric\", ", + { + "pluginId": "lens", + "scope": "public", + "docId": "kibLensPluginApi", + "section": "def-public.MetricVisualizationState", + "text": "MetricVisualizationState" + }, + "> | LensAttributes) => void) | undefined" ], "path": "x-pack/plugins/lens/public/trigger_actions/open_lens_config/in_app_embeddable_edit/types.ts", "deprecated": false, "trackAdoption": false, - "children": [], + "children": [ + { + "parentPluginId": "lens", + "id": "def-public.InlineEditLensEmbeddableContext.onApply.$1", + "type": "CompoundType", + "tags": [], + "label": "newAttributes", + "description": [], + "signature": [ + "LensAttributes<\"lnsXY\", ", + { + "pluginId": "lens", + "scope": "public", + "docId": "kibLensPluginApi", + "section": "def-public.XYState", + "text": "XYState" + }, + "> | LensAttributes<\"lnsPie\", ", + "PieVisualizationState", + "> | LensAttributes<\"lnsHeatmap\", ", + { + "pluginId": "lens", + "scope": "public", + "docId": "kibLensPluginApi", + "section": "def-public.HeatmapVisualizationState", + "text": "HeatmapVisualizationState" + }, + "> | LensAttributes<\"lnsGauge\", ", + { + "pluginId": "lens", + "scope": "public", + "docId": "kibLensPluginApi", + "section": "def-public.GaugeVisualizationState", + "text": "GaugeVisualizationState" + }, + "> | LensAttributes<\"lnsDatatable\", ", + { + "pluginId": "lens", + "scope": "public", + "docId": "kibLensPluginApi", + "section": "def-public.DatatableVisualizationState", + "text": "DatatableVisualizationState" + }, + "> | LensAttributes<\"lnsLegacyMetric\", ", + { + "pluginId": "lens", + "scope": "common", + "docId": "kibLensPluginApi", + "section": "def-common.LegacyMetricState", + "text": "LegacyMetricState" + }, + "> | LensAttributes<\"lnsMetric\", ", + { + "pluginId": "lens", + "scope": "public", + "docId": "kibLensPluginApi", + "section": "def-public.MetricVisualizationState", + "text": "MetricVisualizationState" + }, + "> | LensAttributes" + ], + "path": "x-pack/plugins/lens/public/trigger_actions/open_lens_config/in_app_embeddable_edit/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], "returnComment": [] }, { @@ -10579,7 +10695,7 @@ "section": "def-public.Action", "text": "Action" }, - "[] | undefined; showInspector?: boolean | undefined; }" + "[] | undefined; showInspector?: boolean | undefined; abortController?: AbortController | undefined; }" ], "path": "x-pack/plugins/lens/public/embeddable/embeddable_component.tsx", "deprecated": false, diff --git a/api_docs/lens.mdx b/api_docs/lens.mdx index b32a83969e5c1..b0464814b07d1 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lens'] --- import lensObj from './lens.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/k | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 661 | 0 | 560 | 61 | +| 662 | 0 | 561 | 61 | ## Client diff --git a/api_docs/license_api_guard.mdx b/api_docs/license_api_guard.mdx index a10c19ced4f10..58d2d9c9771b7 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-02-05 +date: 2024-02-07 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 81da5801426f2..0f1e4bf4e4cde 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licenseManagement'] --- import licenseManagementObj from './license_management.devdocs.json'; diff --git a/api_docs/licensing.devdocs.json b/api_docs/licensing.devdocs.json index 5d1c9f467ed28..0d43c4c2d00dc 100644 --- a/api_docs/licensing.devdocs.json +++ b/api_docs/licensing.devdocs.json @@ -2218,6 +2218,14 @@ "plugin": "osquery", "path": "x-pack/plugins/osquery/server/handlers/action/create_action_service.ts" }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/threat_mapping/create_threat_signal.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/threat_mapping/create_event_signal.ts" + }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/query.ts" diff --git a/api_docs/licensing.mdx b/api_docs/licensing.mdx index 30daa1fb630b4..ad5bad5f28d94 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-02-05 +date: 2024-02-07 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 5e52241220caf..3d3f3edd5debd 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-02-05 +date: 2024-02-07 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 918e6884c7714..5af85f966824c 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lists'] --- import listsObj from './lists.devdocs.json'; diff --git a/api_docs/logs_explorer.devdocs.json b/api_docs/logs_explorer.devdocs.json index 07da878441b33..24910c48f35bc 100644 --- a/api_docs/logs_explorer.devdocs.json +++ b/api_docs/logs_explorer.devdocs.json @@ -183,10 +183,10 @@ "interfaces": [ { "parentPluginId": "logsExplorer", - "id": "def-public.LogExplorerController", + "id": "def-public.LogsExplorerController", "type": "Interface", "tags": [], - "label": "LogExplorerController", + "label": "LogsExplorerController", "description": [], "path": "x-pack/plugins/observability_solution/logs_explorer/public/controller/types.ts", "deprecated": false, @@ -194,7 +194,7 @@ "children": [ { "parentPluginId": "logsExplorer", - "id": "def-public.LogExplorerController.actions", + "id": "def-public.LogsExplorerController.actions", "type": "Object", "tags": [], "label": "actions", @@ -208,7 +208,7 @@ }, { "parentPluginId": "logsExplorer", - "id": "def-public.LogExplorerController.customizations", + "id": "def-public.LogsExplorerController.customizations", "type": "Object", "tags": [], "label": "customizations", @@ -218,8 +218,8 @@ "pluginId": "logsExplorer", "scope": "public", "docId": "kibLogsExplorerPluginApi", - "section": "def-public.LogExplorerCustomizations", - "text": "LogExplorerCustomizations" + "section": "def-public.LogsExplorerCustomizations", + "text": "LogsExplorerCustomizations" } ], "path": "x-pack/plugins/observability_solution/logs_explorer/public/controller/types.ts", @@ -228,7 +228,7 @@ }, { "parentPluginId": "logsExplorer", - "id": "def-public.LogExplorerController.datasetsClient", + "id": "def-public.LogsExplorerController.datasetsClient", "type": "Object", "tags": [], "label": "datasetsClient", @@ -242,7 +242,7 @@ }, { "parentPluginId": "logsExplorer", - "id": "def-public.LogExplorerController.discoverServices", + "id": "def-public.LogsExplorerController.discoverServices", "type": "CompoundType", "tags": [], "label": "discoverServices", @@ -266,7 +266,7 @@ }, { "parentPluginId": "logsExplorer", - "id": "def-public.LogExplorerController.event$", + "id": "def-public.LogsExplorerController.event$", "type": "Object", "tags": [], "label": "event$", @@ -281,7 +281,7 @@ }, { "parentPluginId": "logsExplorer", - "id": "def-public.LogExplorerController.service", + "id": "def-public.LogsExplorerController.service", "type": "Object", "tags": [], "label": "service", @@ -355,15 +355,15 @@ " & ", "WithDiscoverStateContainer", "), any, ", - "LogExplorerControllerEvent", + "LogsExplorerControllerEvent", ", ", - "LogExplorerControllerTypeState", + "LogsExplorerControllerTypeState", ", ", "ResolveTypegenMeta", "<", "TypegenDisabled", ", ", - "LogExplorerControllerEvent", + "LogsExplorerControllerEvent", ", ", "BaseActionObject", ", ", @@ -376,7 +376,7 @@ }, { "parentPluginId": "logsExplorer", - "id": "def-public.LogExplorerController.state$", + "id": "def-public.LogsExplorerController.state$", "type": "Object", "tags": [], "label": "state$", @@ -388,8 +388,8 @@ "pluginId": "logsExplorer", "scope": "public", "docId": "kibLogsExplorerPluginApi", - "section": "def-public.LogExplorerPublicState", - "text": "LogExplorerPublicState" + "section": "def-public.LogsExplorerPublicState", + "text": "LogsExplorerPublicState" }, ">" ], @@ -399,7 +399,7 @@ }, { "parentPluginId": "logsExplorer", - "id": "def-public.LogExplorerController.stateMachine", + "id": "def-public.LogsExplorerController.stateMachine", "type": "Object", "tags": [], "label": "stateMachine", @@ -473,9 +473,9 @@ " & ", "WithDiscoverStateContainer", "), any, ", - "LogExplorerControllerEvent", + "LogsExplorerControllerEvent", ", ", - "LogExplorerControllerTypeState", + "LogsExplorerControllerTypeState", ", ", "BaseActionObject", ", ", @@ -485,7 +485,7 @@ "<", "TypegenDisabled", ", ", - "LogExplorerControllerEvent", + "LogsExplorerControllerEvent", ", ", "BaseActionObject", ", ", @@ -501,10 +501,10 @@ }, { "parentPluginId": "logsExplorer", - "id": "def-public.LogExplorerCustomizations", + "id": "def-public.LogsExplorerCustomizations", "type": "Interface", "tags": [], - "label": "LogExplorerCustomizations", + "label": "LogsExplorerCustomizations", "description": [], "path": "x-pack/plugins/observability_solution/logs_explorer/public/customizations/types.ts", "deprecated": false, @@ -512,7 +512,7 @@ "children": [ { "parentPluginId": "logsExplorer", - "id": "def-public.LogExplorerCustomizations.flyout", + "id": "def-public.LogsExplorerCustomizations.flyout", "type": "Object", "tags": [], "label": "flyout", @@ -525,8 +525,8 @@ "pluginId": "logsExplorer", "scope": "public", "docId": "kibLogsExplorerPluginApi", - "section": "def-public.LogExplorerFlyoutContentProps", - "text": "LogExplorerFlyoutContentProps" + "section": "def-public.LogsExplorerFlyoutContentProps", + "text": "LogsExplorerFlyoutContentProps" }, "> | undefined; } | undefined" ], @@ -539,10 +539,10 @@ }, { "parentPluginId": "logsExplorer", - "id": "def-public.LogExplorerFlyoutContentProps", + "id": "def-public.LogsExplorerFlyoutContentProps", "type": "Interface", "tags": [], - "label": "LogExplorerFlyoutContentProps", + "label": "LogsExplorerFlyoutContentProps", "description": [], "path": "x-pack/plugins/observability_solution/logs_explorer/public/customizations/types.ts", "deprecated": false, @@ -550,7 +550,7 @@ "children": [ { "parentPluginId": "logsExplorer", - "id": "def-public.LogExplorerFlyoutContentProps.actions", + "id": "def-public.LogsExplorerFlyoutContentProps.actions", "type": "Object", "tags": [], "label": "actions", @@ -566,7 +566,7 @@ }, { "parentPluginId": "logsExplorer", - "id": "def-public.LogExplorerFlyoutContentProps.dataView", + "id": "def-public.LogsExplorerFlyoutContentProps.dataView", "type": "Object", "tags": [], "label": "dataView", @@ -586,7 +586,7 @@ }, { "parentPluginId": "logsExplorer", - "id": "def-public.LogExplorerFlyoutContentProps.doc", + "id": "def-public.LogsExplorerFlyoutContentProps.doc", "type": "Object", "tags": [], "label": "doc", @@ -606,10 +606,10 @@ "misc": [ { "parentPluginId": "logsExplorer", - "id": "def-public.CreateLogExplorerController", + "id": "def-public.CreateLogsExplorerController", "type": "Type", "tags": [], - "label": "CreateLogExplorerController", + "label": "CreateLogsExplorerController", "description": [], "signature": [ "({ customizations, initialState, }: { customizations?: ", @@ -617,24 +617,24 @@ "pluginId": "logsExplorer", "scope": "public", "docId": "kibLogsExplorerPluginApi", - "section": "def-public.LogExplorerCustomizations", - "text": "LogExplorerCustomizations" + "section": "def-public.LogsExplorerCustomizations", + "text": "LogsExplorerCustomizations" }, " | undefined; initialState?: ", { "pluginId": "logsExplorer", "scope": "public", "docId": "kibLogsExplorerPluginApi", - "section": "def-public.LogExplorerPublicStateUpdate", - "text": "LogExplorerPublicStateUpdate" + "section": "def-public.LogsExplorerPublicStateUpdate", + "text": "LogsExplorerPublicStateUpdate" }, " | undefined; }) => Promise<", { "pluginId": "logsExplorer", "scope": "public", "docId": "kibLogsExplorerPluginApi", - "section": "def-public.LogExplorerController", - "text": "LogExplorerController" + "section": "def-public.LogsExplorerController", + "text": "LogsExplorerController" }, ">" ], @@ -645,7 +645,7 @@ "children": [ { "parentPluginId": "logsExplorer", - "id": "def-public.CreateLogExplorerController.$1", + "id": "def-public.CreateLogsExplorerController.$1", "type": "Object", "tags": [], "label": "__0", @@ -656,16 +656,16 @@ "pluginId": "logsExplorer", "scope": "public", "docId": "kibLogsExplorerPluginApi", - "section": "def-public.LogExplorerCustomizations", - "text": "LogExplorerCustomizations" + "section": "def-public.LogsExplorerCustomizations", + "text": "LogsExplorerCustomizations" }, " | undefined; initialState?: ", { "pluginId": "logsExplorer", "scope": "public", "docId": "kibLogsExplorerPluginApi", - "section": "def-public.LogExplorerPublicStateUpdate", - "text": "LogExplorerPublicStateUpdate" + "section": "def-public.LogsExplorerPublicStateUpdate", + "text": "LogsExplorerPublicStateUpdate" }, " | undefined; }" ], @@ -678,10 +678,10 @@ }, { "parentPluginId": "logsExplorer", - "id": "def-public.LogExplorerControllerContext", + "id": "def-public.LogsExplorerControllerContext", "type": "Type", "tags": [], - "label": "LogExplorerControllerContext", + "label": "LogsExplorerControllerContext", "description": [], "signature": [ "(", @@ -752,17 +752,17 @@ "WithDiscoverStateContainer", ")" ], - "path": "x-pack/plugins/observability_solution/logs_explorer/public/state_machines/log_explorer_controller/src/types.ts", + "path": "x-pack/plugins/observability_solution/logs_explorer/public/state_machines/logs_explorer_controller/src/types.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false }, { "parentPluginId": "logsExplorer", - "id": "def-public.LogExplorerPublicState", + "id": "def-public.LogsExplorerPublicState", "type": "Type", "tags": [], - "label": "LogExplorerPublicState", + "label": "LogsExplorerPublicState", "description": [], "signature": [ { @@ -799,10 +799,10 @@ }, { "parentPluginId": "logsExplorer", - "id": "def-public.LogExplorerPublicStateUpdate", + "id": "def-public.LogsExplorerPublicStateUpdate", "type": "Type", "tags": [], - "label": "LogExplorerPublicStateUpdate", + "label": "LogsExplorerPublicStateUpdate", "description": [], "signature": [ { @@ -841,10 +841,10 @@ "objects": [], "setup": { "parentPluginId": "logsExplorer", - "id": "def-public.LogExplorerPluginSetup", + "id": "def-public.LogsExplorerPluginSetup", "type": "Interface", "tags": [], - "label": "LogExplorerPluginSetup", + "label": "LogsExplorerPluginSetup", "description": [], "path": "x-pack/plugins/observability_solution/logs_explorer/public/types.ts", "deprecated": false, @@ -852,13 +852,13 @@ "children": [ { "parentPluginId": "logsExplorer", - "id": "def-public.LogExplorerPluginSetup.locators", + "id": "def-public.LogsExplorerPluginSetup.locators", "type": "Object", "tags": [], "label": "locators", "description": [], "signature": [ - "LogExplorerLocators" + "LogsExplorerLocators" ], "path": "x-pack/plugins/observability_solution/logs_explorer/public/types.ts", "deprecated": false, @@ -870,10 +870,10 @@ }, "start": { "parentPluginId": "logsExplorer", - "id": "def-public.LogExplorerPluginStart", + "id": "def-public.LogsExplorerPluginStart", "type": "Interface", "tags": [], - "label": "LogExplorerPluginStart", + "label": "LogsExplorerPluginStart", "description": [], "path": "x-pack/plugins/observability_solution/logs_explorer/public/types.ts", "deprecated": false, @@ -881,16 +881,16 @@ "children": [ { "parentPluginId": "logsExplorer", - "id": "def-public.LogExplorerPluginStart.LogExplorer", + "id": "def-public.LogsExplorerPluginStart.LogsExplorer", "type": "CompoundType", "tags": [], - "label": "LogExplorer", + "label": "LogsExplorer", "description": [], "signature": [ "React.ComponentClass<", - "LogExplorerProps", + "LogsExplorerProps", ", any> | React.FunctionComponent<", - "LogExplorerProps", + "LogsExplorerProps", ">" ], "path": "x-pack/plugins/observability_solution/logs_explorer/public/types.ts", @@ -899,10 +899,10 @@ }, { "parentPluginId": "logsExplorer", - "id": "def-public.LogExplorerPluginStart.createLogExplorerController", + "id": "def-public.LogsExplorerPluginStart.createLogsExplorerController", "type": "Function", "tags": [], - "label": "createLogExplorerController", + "label": "createLogsExplorerController", "description": [], "signature": [ "({ customizations, initialState, }: { customizations?: ", @@ -910,24 +910,24 @@ "pluginId": "logsExplorer", "scope": "public", "docId": "kibLogsExplorerPluginApi", - "section": "def-public.LogExplorerCustomizations", - "text": "LogExplorerCustomizations" + "section": "def-public.LogsExplorerCustomizations", + "text": "LogsExplorerCustomizations" }, " | undefined; initialState?: ", { "pluginId": "logsExplorer", "scope": "public", "docId": "kibLogsExplorerPluginApi", - "section": "def-public.LogExplorerPublicStateUpdate", - "text": "LogExplorerPublicStateUpdate" + "section": "def-public.LogsExplorerPublicStateUpdate", + "text": "LogsExplorerPublicStateUpdate" }, " | undefined; }) => Promise<", { "pluginId": "logsExplorer", "scope": "public", "docId": "kibLogsExplorerPluginApi", - "section": "def-public.LogExplorerController", - "text": "LogExplorerController" + "section": "def-public.LogsExplorerController", + "text": "LogsExplorerController" }, ">" ], @@ -938,7 +938,7 @@ "children": [ { "parentPluginId": "logsExplorer", - "id": "def-public.LogExplorerPluginStart.createLogExplorerController.$1", + "id": "def-public.LogsExplorerPluginStart.createLogsExplorerController.$1", "type": "Object", "tags": [], "label": "__0", @@ -949,16 +949,16 @@ "pluginId": "logsExplorer", "scope": "public", "docId": "kibLogsExplorerPluginApi", - "section": "def-public.LogExplorerCustomizations", - "text": "LogExplorerCustomizations" + "section": "def-public.LogsExplorerCustomizations", + "text": "LogsExplorerCustomizations" }, " | undefined; initialState?: ", { "pluginId": "logsExplorer", "scope": "public", "docId": "kibLogsExplorerPluginApi", - "section": "def-public.LogExplorerPublicStateUpdate", - "text": "LogExplorerPublicStateUpdate" + "section": "def-public.LogsExplorerPublicStateUpdate", + "text": "LogsExplorerPublicStateUpdate" }, " | undefined; }" ], diff --git a/api_docs/logs_explorer.mdx b/api_docs/logs_explorer.mdx index ad5a77572388a..09de1f2649d93 100644 --- a/api_docs/logs_explorer.mdx +++ b/api_docs/logs_explorer.mdx @@ -8,12 +8,12 @@ slug: /kibana-dev-docs/api/logsExplorer title: "logsExplorer" image: https://source.unsplash.com/400x175/?github description: API docs for the logsExplorer plugin -date: 2024-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'logsExplorer'] --- import logsExplorerObj from './logs_explorer.devdocs.json'; -This plugin provides a LogExplorer component using the Discover customization framework, offering several affordances specifically designed for log consumption. +This plugin provides a LogsExplorer component using the Discover customization framework, offering several affordances specifically designed for log consumption. Contact [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) for questions regarding this plugin. diff --git a/api_docs/logs_shared.devdocs.json b/api_docs/logs_shared.devdocs.json index e1e789c745eba..fd2f4b7d5cc16 100644 --- a/api_docs/logs_shared.devdocs.json +++ b/api_docs/logs_shared.devdocs.json @@ -6419,7 +6419,7 @@ }, " | undefined; fixedInterval?: string[] | undefined; timeZone?: string[] | undefined; timeSeriesDimension?: boolean | undefined; timeSeriesMetric?: ", "MappingTimeSeriesMetricType", - " | undefined; shortDotsEnable?: boolean | undefined; isMapped?: boolean | undefined; parentName?: string | undefined; }" + " | undefined; shortDotsEnable?: boolean | undefined; isMapped?: boolean | undefined; parentName?: string | undefined; defaultFormatter?: string | undefined; }" ], "path": "x-pack/plugins/logs_shared/common/log_views/resolved_log_view.ts", "deprecated": false, diff --git a/api_docs/logs_shared.mdx b/api_docs/logs_shared.mdx index ab4e8e52539e9..2679d57071b61 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'logsShared'] --- import logsSharedObj from './logs_shared.devdocs.json'; diff --git a/api_docs/management.mdx b/api_docs/management.mdx index 01f293fa836be..f0162cca4a28f 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-02-05 +date: 2024-02-07 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 e8dad4c71b995..a167d3a2f1c43 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-02-05 +date: 2024-02-07 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 7ebb8524dfa95..09130b89cd557 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'mapsEms'] --- import mapsEmsObj from './maps_ems.devdocs.json'; diff --git a/api_docs/metrics_data_access.mdx b/api_docs/metrics_data_access.mdx index 19574e0646a51..4f41218ac4548 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'metricsDataAccess'] --- import metricsDataAccessObj from './metrics_data_access.devdocs.json'; diff --git a/api_docs/ml.mdx b/api_docs/ml.mdx index e64524225744c..4e67147a05497 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-02-05 +date: 2024-02-07 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 1f480159632d9..cc67220cd5dea 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-02-05 +date: 2024-02-07 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 1326e0cb2783b..10874e9c8b9cc 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-02-05 +date: 2024-02-07 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 e44390e85d135..f2a2a8830e3d1 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'monitoringCollection'] --- import monitoringCollectionObj from './monitoring_collection.devdocs.json'; diff --git a/api_docs/navigation.mdx b/api_docs/navigation.mdx index d82a308f85ed2..a789717fe52c1 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'navigation'] --- import navigationObj from './navigation.devdocs.json'; diff --git a/api_docs/newsfeed.mdx b/api_docs/newsfeed.mdx index 7f56ed7f80398..5f0e641c2ef98 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-02-05 +date: 2024-02-07 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 46e954bdedbf9..151e94c5fae16 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-02-05 +date: 2024-02-07 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 0e0437b7d60ae..096dac0cf4d67 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-02-05 +date: 2024-02-07 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 57cac0c0530e2..61c5c7cd3c8c1 100644 --- a/api_docs/observability.devdocs.json +++ b/api_docs/observability.devdocs.json @@ -8417,7 +8417,45 @@ "label": "ObservabilityAPIReturnType", "description": [], "signature": [ - "{ \"POST /api/observability/slos/{id}/_reset 2023-10-31\": { endpoint: \"POST /api/observability/slos/{id}/_reset 2023-10-31\"; params?: ", + "{ \"GET /internal/api/observability/slos/_groups\": { endpoint: \"GET /internal/api/observability/slos/_groups\"; params?: ", + "PartialC", + "<{ query: ", + "PartialC", + "<{ page: ", + "StringC", + "; perPage: ", + "StringC", + "; groupBy: ", + "UnionC", + "<[", + "LiteralC", + "<\"ungrouped\">, ", + "LiteralC", + "<\"slo.tags\">, ", + "LiteralC", + "<\"status\">, ", + "LiteralC", + "<\"slo.indicator.type\">]>; kqlQuery: ", + "StringC", + "; filters: ", + "StringC", + "; }>; }> | undefined; handler: ({}: ", + { + "pluginId": "observability", + "scope": "server", + "docId": "kibObservabilityPluginApi", + "section": "def-server.ObservabilityRouteHandlerResources", + "text": "ObservabilityRouteHandlerResources" + }, + " & { params?: { query?: { page?: string | undefined; perPage?: string | undefined; groupBy?: \"status\" | \"ungrouped\" | \"slo.tags\" | \"slo.indicator.type\" | undefined; kqlQuery?: string | undefined; filters?: string | undefined; } | undefined; } | undefined; }) => Promise<{ page: number; perPage: number; total: number; results: { group: string; groupBy: string; summary: { total: number; worst: { sliValue: number; status: string; slo: { id: string; instanceId: string; name: string; }; }; violated: number; healthy: number; degrading: number; noData: number; }; }[]; }>; } & ", + { + "pluginId": "observability", + "scope": "server", + "docId": "kibObservabilityPluginApi", + "section": "def-server.ObservabilityRouteCreateOptions", + "text": "ObservabilityRouteCreateOptions" + }, + "; \"POST /api/observability/slos/{id}/_reset 2023-10-31\": { endpoint: \"POST /api/observability/slos/{id}/_reset 2023-10-31\"; params?: ", "TypeC", "<{ path: ", "TypeC", @@ -10725,7 +10763,45 @@ "label": "ObservabilityServerRouteRepository", "description": [], "signature": [ - "{ \"POST /api/observability/slos/{id}/_reset 2023-10-31\": { endpoint: \"POST /api/observability/slos/{id}/_reset 2023-10-31\"; params?: ", + "{ \"GET /internal/api/observability/slos/_groups\": { endpoint: \"GET /internal/api/observability/slos/_groups\"; params?: ", + "PartialC", + "<{ query: ", + "PartialC", + "<{ page: ", + "StringC", + "; perPage: ", + "StringC", + "; groupBy: ", + "UnionC", + "<[", + "LiteralC", + "<\"ungrouped\">, ", + "LiteralC", + "<\"slo.tags\">, ", + "LiteralC", + "<\"status\">, ", + "LiteralC", + "<\"slo.indicator.type\">]>; kqlQuery: ", + "StringC", + "; filters: ", + "StringC", + "; }>; }> | undefined; handler: ({}: ", + { + "pluginId": "observability", + "scope": "server", + "docId": "kibObservabilityPluginApi", + "section": "def-server.ObservabilityRouteHandlerResources", + "text": "ObservabilityRouteHandlerResources" + }, + " & { params?: { query?: { page?: string | undefined; perPage?: string | undefined; groupBy?: \"status\" | \"ungrouped\" | \"slo.tags\" | \"slo.indicator.type\" | undefined; kqlQuery?: string | undefined; filters?: string | undefined; } | undefined; } | undefined; }) => Promise<{ page: number; perPage: number; total: number; results: { group: string; groupBy: string; summary: { total: number; worst: { sliValue: number; status: string; slo: { id: string; instanceId: string; name: string; }; }; violated: number; healthy: number; degrading: number; noData: number; }; }[]; }>; } & ", + { + "pluginId": "observability", + "scope": "server", + "docId": "kibObservabilityPluginApi", + "section": "def-server.ObservabilityRouteCreateOptions", + "text": "ObservabilityRouteCreateOptions" + }, + "; \"POST /api/observability/slos/{id}/_reset 2023-10-31\": { endpoint: \"POST /api/observability/slos/{id}/_reset 2023-10-31\"; params?: ", "TypeC", "<{ path: ", "TypeC", @@ -14313,6 +14389,90 @@ } ] }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.enableInfrastructureHostsCustomDashboards", + "type": "Object", + "tags": [], + "label": "[enableInfrastructureHostsCustomDashboards]", + "description": [], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.enableInfrastructureHostsCustomDashboards.category", + "type": "Array", + "tags": [], + "label": "category", + "description": [], + "signature": [ + "string[]" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.enableInfrastructureHostsCustomDashboards.name", + "type": "string", + "tags": [], + "label": "name", + "description": [], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.enableInfrastructureHostsCustomDashboards.value", + "type": "boolean", + "tags": [], + "label": "value", + "description": [], + "signature": [ + "false" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.enableInfrastructureHostsCustomDashboards.description", + "type": "string", + "tags": [], + "label": "description", + "description": [], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.uiSettings.enableInfrastructureHostsCustomDashboards.schema", + "type": "Object", + "tags": [], + "label": "schema", + "description": [], + "signature": [ + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "" + ], + "path": "x-pack/plugins/observability/server/ui_settings.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, { "parentPluginId": "observability", "id": "def-server.uiSettings.enableAwsLambdaMetrics", @@ -17352,6 +17512,21 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "observability", + "id": "def-common.enableInfrastructureHostsCustomDashboards", + "type": "string", + "tags": [], + "label": "enableInfrastructureHostsCustomDashboards", + "description": [], + "signature": [ + "\"observability:enableInfrastructureHostsCustomDashboards\"" + ], + "path": "x-pack/plugins/observability/common/ui_settings_keys.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "observability", "id": "def-common.enableInfrastructureHostsView", diff --git a/api_docs/observability.mdx b/api_docs/observability.mdx index 326dc0dbe6dd2..04d5141c71f8a 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-02-05 +date: 2024-02-07 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 | |-------------------|-----------|------------------------|-----------------| -| 619 | 2 | 610 | 17 | +| 626 | 2 | 617 | 17 | ## Client diff --git a/api_docs/observability_a_i_assistant.devdocs.json b/api_docs/observability_a_i_assistant.devdocs.json index 43245640d1a3d..108c6df21a6db 100644 --- a/api_docs/observability_a_i_assistant.devdocs.json +++ b/api_docs/observability_a_i_assistant.devdocs.json @@ -808,7 +808,9 @@ "IntersectionC", "<[", "TypeC", - "<{ messages: ", + "<{ name: ", + "StringC", + "; messages: ", "ArrayC", "<", "Type", @@ -846,7 +848,7 @@ "StringC", "; }>]>; }> | undefined; handler: ({}: ", "ObservabilityAIAssistantRouteHandlerResources", - " & { params: { body: { messages: ", + " & { params: { body: { name: string; messages: ", { "pluginId": "observabilityAIAssistant", "scope": "common", @@ -1304,7 +1306,9 @@ "IntersectionC", "<[", "TypeC", - "<{ messages: ", + "<{ name: ", + "StringC", + "; messages: ", "ArrayC", "<", "Type", @@ -1342,7 +1346,7 @@ "StringC", "; }>]>; }> | undefined; handler: ({}: ", "ObservabilityAIAssistantRouteHandlerResources", - " & { params: { body: { messages: ", + " & { params: { body: { name: string; messages: ", { "pluginId": "observabilityAIAssistant", "scope": "common", @@ -2040,7 +2044,9 @@ "IntersectionC", "<[", "TypeC", - "<{ messages: ", + "<{ name: ", + "StringC", + "; messages: ", "ArrayC", "<", "Type", @@ -2078,7 +2084,7 @@ "StringC", "; }>]>; }> | undefined; handler: ({}: ", "ObservabilityAIAssistantRouteHandlerResources", - " & { params: { body: { messages: ", + " & { params: { body: { name: string; messages: ", { "pluginId": "observabilityAIAssistant", "scope": "common", @@ -2545,7 +2551,9 @@ "IntersectionC", "<[", "TypeC", - "<{ messages: ", + "<{ name: ", + "StringC", + "; messages: ", "ArrayC", "<", "Type", @@ -2583,7 +2591,7 @@ "StringC", "; }>]>; }> | undefined; handler: ({}: ", "ObservabilityAIAssistantRouteHandlerResources", - " & { params: { body: { messages: ", + " & { params: { body: { name: string; messages: ", { "pluginId": "observabilityAIAssistant", "scope": "common", @@ -3194,7 +3202,9 @@ "IntersectionC", "<[", "TypeC", - "<{ messages: ", + "<{ name: ", + "StringC", + "; messages: ", "ArrayC", "<", "Type", @@ -3232,7 +3242,7 @@ "StringC", "; }>]>; }> | undefined; handler: ({}: ", "ObservabilityAIAssistantRouteHandlerResources", - " & { params: { body: { messages: ", + " & { params: { body: { name: string; messages: ", { "pluginId": "observabilityAIAssistant", "scope": "common", diff --git a/api_docs/observability_a_i_assistant.mdx b/api_docs/observability_a_i_assistant.mdx index f737c49722bfa..a4fd4c7d0970d 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityAIAssistant'] --- import observabilityAIAssistantObj from './observability_a_i_assistant.devdocs.json'; diff --git a/api_docs/observability_logs_explorer.devdocs.json b/api_docs/observability_logs_explorer.devdocs.json index d82d633baa66e..5c60b9f47b230 100644 --- a/api_docs/observability_logs_explorer.devdocs.json +++ b/api_docs/observability_logs_explorer.devdocs.json @@ -321,10 +321,10 @@ "interfaces": [ { "parentPluginId": "observabilityLogsExplorer", - "id": "def-common.ObservabilityLogExplorerLocators", + "id": "def-common.ObservabilityLogsExplorerLocators", "type": "Interface", "tags": [], - "label": "ObservabilityLogExplorerLocators", + "label": "ObservabilityLogsExplorerLocators", "description": [], "path": "x-pack/plugins/observability_solution/observability_logs_explorer/common/locators/index.ts", "deprecated": false, @@ -332,7 +332,7 @@ "children": [ { "parentPluginId": "observabilityLogsExplorer", - "id": "def-common.ObservabilityLogExplorerLocators.allDatasetsLocator", + "id": "def-common.ObservabilityLogsExplorerLocators.allDatasetsLocator", "type": "Object", "tags": [], "label": "allDatasetsLocator", @@ -361,7 +361,7 @@ }, { "parentPluginId": "observabilityLogsExplorer", - "id": "def-common.ObservabilityLogExplorerLocators.singleDatasetLocator", + "id": "def-common.ObservabilityLogsExplorerLocators.singleDatasetLocator", "type": "Object", "tags": [], "label": "singleDatasetLocator", diff --git a/api_docs/observability_logs_explorer.mdx b/api_docs/observability_logs_explorer.mdx index 021b6a08c30a4..d71b6d3b1369b 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-02-05 +date: 2024-02-07 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 05983dd0d2d61..5a860c5feb077 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityOnboarding'] --- import observabilityOnboardingObj from './observability_onboarding.devdocs.json'; diff --git a/api_docs/observability_shared.devdocs.json b/api_docs/observability_shared.devdocs.json index bdb4333fe1c11..6f23842fcb285 100644 --- a/api_docs/observability_shared.devdocs.json +++ b/api_docs/observability_shared.devdocs.json @@ -4101,10 +4101,10 @@ }, { "parentPluginId": "observabilityShared", - "id": "def-common.LOG_EXPLORER_FEEDBACK_LINK", + "id": "def-common.LOGS_EXPLORER_FEEDBACK_LINK", "type": "string", "tags": [], - "label": "LOG_EXPLORER_FEEDBACK_LINK", + "label": "LOGS_EXPLORER_FEEDBACK_LINK", "description": [], "signature": [ "\"https://ela.st/explorer-feedback\"" diff --git a/api_docs/observability_shared.mdx b/api_docs/observability_shared.mdx index 3b5e0ec45aef6..4bc22a31b5e3d 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-02-05 +date: 2024-02-07 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 c6268b34f1045..2fbd67b92f0fa 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-02-05 +date: 2024-02-07 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 ecd718cbb9799..1b44890f84450 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-02-05 +date: 2024-02-07 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 0e8edf24d362a..dd622bf6e51c6 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -21,7 +21,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | API Count | Any Count | Missing comments | Missing exports | |--------------|----------|-----------------|--------| -| 79642 | 229 | 68220 | 1727 | +| 79725 | 228 | 68291 | 1734 | ## Plugin Directory @@ -31,8 +31,8 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/appex-sharedux @elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/appex-sharedux ) | - | 2 | 0 | 2 | 0 | | | [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs-knowledge-team) | - | 2 | 0 | 2 | 0 | | | [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs-knowledge-team) | - | 2 | 0 | 2 | 0 | -| | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | AIOps plugin maintained by ML team. | 70 | 1 | 4 | 1 | -| | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 834 | 1 | 803 | 51 | +| | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | AIOps plugin maintained by ML team. | 69 | 0 | 4 | 1 | +| | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 835 | 1 | 804 | 51 | | | [@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 | 125 | | | [@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) | Asset manager plugin for entity assets (inventory, topology, etc) | 9 | 0 | 9 | 2 | @@ -50,26 +50,26 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | 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/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 29 | 0 | 23 | 0 | -| | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Content management app | 149 | 0 | 126 | 6 | +| | [@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 | 323 | 0 | 315 | 16 | | crossClusterReplication | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 0 | 0 | 0 | 0 | | customBranding | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Enables customization of Kibana | 0 | 0 | 0 | 0 | | | [@elastic/fleet](https://github.com/orgs/elastic/teams/fleet) | Add custom data integrations so they can be displayed in the Fleet integrations app | 268 | 0 | 249 | 1 | | | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds the Dashboard app to Kibana | 108 | 0 | 105 | 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. | 3235 | 31 | 2583 | 23 | +| | [@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. | 3237 | 31 | 2585 | 23 | | | [@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 | 0 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | Data view management app | 2 | 0 | 2 | 0 | -| | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | Data services are useful for searching and querying data from Elasticsearch. Helpful utilities include: a re-usable react query bar, KQL autocomplete, async search, Data Views (Index Patterns) and field formatters. | 940 | 0 | 273 | 4 | +| | [@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. | 943 | 0 | 276 | 4 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | The Data Visualizer tools help you understand your data, by analyzing the metrics and fields in a log file or an existing Elasticsearch index. | 31 | 3 | 25 | 1 | | | [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) | This plugin introduces the concept of dataset quality, where users can easily get an overview on the datasets they have. | 10 | 0 | 10 | 5 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 12 | 0 | 10 | 3 | -| | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | This plugin contains the Discover application and the saved search embeddable. | 141 | 0 | 95 | 22 | +| | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | This plugin contains the Discover application and the saved search embeddable. | 151 | 0 | 104 | 22 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 37 | 0 | 35 | 2 | -| | [@elastic/security-threat-hunting-investigations](https://github.com/orgs/elastic/teams/security-threat-hunting-investigations) | APIs used to assess the quality of data in Elasticsearch indexes | 2 | 0 | 0 | 0 | +| | [@elastic/security-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 | 41 | 0 | 27 | 0 | -| | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds embeddables service to Kibana | 564 | 1 | 459 | 8 | +| | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds embeddables service to Kibana | 557 | 1 | 452 | 8 | | | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Extends embeddable plugin with more functionality | 14 | 0 | 14 | 0 | | | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | This plugin provides encryption and decryption utilities for saved objects containing sensitive information. | 53 | 0 | 46 | 1 | | | [@elastic/enterprise-search-frontend](https://github.com/orgs/elastic/teams/enterprise-search-frontend) | Adds dashboards for discovering and managing Enterprise Search products. | 5 | 0 | 5 | 0 | @@ -91,13 +91,13 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds 'shape' function and renderer to expressions | 148 | 0 | 146 | 0 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | Expression Tagcloud plugin adds a `tagcloud` renderer and function to the expression plugin. The renderer will display the `Wordcloud` chart. | 6 | 0 | 6 | 2 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | Expression XY plugin adds a `xy` renderer and function to the expression plugin. The renderer will display the `xy` chart. | 177 | 0 | 167 | 13 | -| | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | Adds expression runtime to Kibana | 2222 | 17 | 1760 | 5 | +| | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | Adds expression runtime to Kibana | 2224 | 17 | 1762 | 5 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 235 | 0 | 99 | 2 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | Index pattern fields and ambiguous values formatters | 292 | 5 | 253 | 3 | | | [@elastic/kibana-gis](https://github.com/orgs/elastic/teams/kibana-gis) | The file upload plugin contains components and services for uploading a file, analyzing its data, and then importing the data into an Elasticsearch index. Supported file types include CSV, TSV, newline-delimited JSON and GeoJSON. | 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 | 2 | 0 | 2 | 0 | -| | [@elastic/fleet](https://github.com/orgs/elastic/teams/fleet) | - | 1221 | 3 | 1104 | 51 | +| | [@elastic/fleet](https://github.com/orgs/elastic/teams/fleet) | - | 1229 | 3 | 1110 | 54 | | ftrApis | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 0 | 0 | 0 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 68 | 0 | 14 | 5 | | globalSearchBar | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 0 | 0 | 0 | 0 | @@ -108,24 +108,24 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 151 | 0 | 111 | 1 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Image embeddable | 3 | 0 | 3 | 1 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 4 | 0 | 4 | 0 | -| | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 215 | 0 | 210 | 4 | +| | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 223 | 0 | 218 | 4 | | | [@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 | 32 | 0 | 29 | 8 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-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/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/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) | - | 152 | 0 | 120 | 3 | +| | [@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 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 609 | 3 | 416 | 9 | | | [@elastic/kibana-cloud-security-posture](https://github.com/orgs/elastic/teams/kibana-cloud-security-posture) | - | 5 | 0 | 5 | 1 | -| | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | Visualization editor allowing to quickly and easily configure compelling visualizations to use on dashboards and canvas workpads. Exposes components to embed visualizations and link into the Lens editor from within other apps in Kibana. | 661 | 0 | 560 | 61 | +| | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | Visualization editor allowing to quickly and easily configure compelling visualizations to use on dashboards and canvas workpads. Exposes components to embed visualizations and link into the Lens editor from within other apps in Kibana. | 662 | 0 | 561 | 61 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 8 | 0 | 8 | 0 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 4 | 0 | 4 | 1 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 117 | 0 | 42 | 10 | | | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | A dashboard panel for creating links to dashboards or external links. | 57 | 0 | 57 | 6 | | | [@elastic/security-detection-engine](https://github.com/orgs/elastic/teams/security-detection-engine) | - | 224 | 0 | 96 | 51 | -| | [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) | This plugin provides a LogExplorer component using the Discover customization framework, offering several affordances specifically designed for log consumption. | 87 | 0 | 87 | 16 | +| | [@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. | 87 | 0 | 87 | 16 | | | [@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. | 302 | 0 | 276 | 32 | | logstash | [@elastic/logstash](https://github.com/orgs/elastic/teams/logstash) | - | 0 | 0 | 0 | 0 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 45 | 0 | 45 | 7 | @@ -140,7 +140,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 17 | 0 | 17 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 3 | 0 | 3 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 2 | 0 | 2 | 1 | -| | [@elastic/obs-ux-management-team](https://github.com/orgs/elastic/teams/obs-ux-management-team) | - | 619 | 2 | 610 | 17 | +| | [@elastic/obs-ux-management-team](https://github.com/orgs/elastic/teams/obs-ux-management-team) | - | 626 | 2 | 617 | 17 | | | [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs-knowledge-team) | - | 75 | 0 | 73 | 13 | | | [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) | This plugin exposes and registers observability log consumption features. | 19 | 0 | 19 | 1 | | | [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) | - | 14 | 0 | 14 | 0 | @@ -154,7 +154,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 23 | 0 | 23 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Reporting Services enables applications to feature reports that the user can automate with Watcher and download later. | 22 | 0 | 6 | 0 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 21 | 0 | 21 | 0 | -| | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 268 | 0 | 239 | 14 | +| | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 272 | 0 | 243 | 14 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 24 | 0 | 19 | 2 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 129 | 2 | 118 | 4 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 25 | 0 | 25 | 0 | @@ -173,7 +173,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Serverless customizations for observability. | 6 | 0 | 6 | 0 | | | [@elastic/enterprise-search-frontend](https://github.com/orgs/elastic/teams/enterprise-search-frontend) | Serverless customizations for search. | 6 | 0 | 6 | 0 | | | [@elastic/kibana-cloud-security-posture](https://github.com/orgs/elastic/teams/kibana-cloud-security-posture) | - | 134 | 0 | 134 | 8 | -| | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Adds URL Service and sharing capabilities to Kibana | 119 | 0 | 60 | 10 | +| | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Adds URL Service and sharing capabilities to Kibana | 119 | 0 | 60 | 11 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 22 | 1 | 22 | 1 | | | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | This plugin provides the Spaces feature, which allows saved objects to be organized into meaningful categories. | 256 | 0 | 65 | 0 | | | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 23 | 0 | 23 | 3 | @@ -190,7 +190,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@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) | - | 596 | 1 | 570 | 58 | -| | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Adds UI Actions service to Kibana | 149 | 0 | 103 | 9 | +| | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Adds UI Actions service to Kibana | 147 | 0 | 101 | 9 | | | [@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). | 10 | 0 | 7 | 2 | | | [@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. | 55 | 0 | 23 | 2 | @@ -253,10 +253,10 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 3 | 0 | 3 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 62 | 0 | 17 | 1 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 2 | 0 | 2 | 0 | -| | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 36 | 0 | 15 | 0 | +| | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 37 | 0 | 15 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 2 | 0 | 2 | 0 | | | [@elastic/appex-qa](https://github.com/orgs/elastic/teams/appex-qa) | - | 8 | 0 | 4 | 0 | -| | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 206 | 0 | 169 | 8 | +| | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 211 | 0 | 174 | 8 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 76 | 0 | 47 | 9 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 24 | 0 | 24 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 140 | 3 | 137 | 18 | @@ -434,7 +434,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 14 | 0 | 9 | 0 | | | [@elastic/security-threat-hunting-explore](https://github.com/orgs/elastic/teams/security-threat-hunting-explore) | - | 80 | 0 | 80 | 1 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 44 | 0 | 43 | 0 | -| | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 5 | 0 | 5 | 0 | +| | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 6 | 0 | 6 | 0 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 5 | 0 | 5 | 0 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 4 | 0 | 4 | 0 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 3 | 0 | 3 | 0 | @@ -454,28 +454,28 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 39 | 0 | 26 | 5 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 19 | 0 | 11 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 35125 | 0 | 34718 | 0 | -| | [@elastic/security-threat-hunting-investigations](https://github.com/orgs/elastic/teams/security-threat-hunting-investigations) | - | 13 | 0 | 5 | 0 | +| | [@elastic/security-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) | - | 104 | 0 | 84 | 6 | | | [@elastic/security-generative-ai](https://github.com/orgs/elastic/teams/security-generative-ai) | - | 59 | 0 | 57 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 52 | 0 | 37 | 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) | - | 7 | 0 | 3 | 0 | -| | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 261 | 1 | 201 | 15 | -| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 31 | 0 | 31 | 0 | +| | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 263 | 1 | 202 | 15 | +| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 32 | 0 | 32 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 2 | 0 | 1 | 0 | -| | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 11 | 0 | 11 | 0 | +| | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 14 | 0 | 13 | 0 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 39 | 0 | 39 | 0 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 52 | 0 | 52 | 1 | | | [@elastic/security-threat-hunting-investigations](https://github.com/orgs/elastic/teams/security-threat-hunting-investigations) | - | 39 | 0 | 15 | 1 | -| | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 20 | 0 | 16 | 0 | +| | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 22 | 0 | 18 | 0 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 38 | 0 | 30 | 1 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 2 | 0 | 0 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 31 | 0 | 31 | 1 | | | [@elastic/appex-qa](https://github.com/orgs/elastic/teams/appex-qa) | - | 550 | 6 | 510 | 2 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 1 | 0 | 0 | 0 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 1 | 0 | 1 | 0 | -| | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 25 | 0 | 25 | 1 | +| | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 26 | 0 | 26 | 1 | | | [@elastic/platform-onboarding](https://github.com/orgs/elastic/teams/platform-onboarding) | - | 49 | 0 | 47 | 0 | | | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | - | 33 | 3 | 24 | 6 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 3 | 0 | 3 | 0 | @@ -506,7 +506,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 23 | 0 | 7 | 0 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 8 | 0 | 2 | 3 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 45 | 0 | 0 | 0 | -| | [@elastic/appex-sharedux @elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/appex-sharedux ) | - | 133 | 0 | 131 | 0 | +| | [@elastic/appex-sharedux @elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/appex-sharedux ) | - | 134 | 0 | 132 | 0 | | | [@elastic/appex-sharedux @elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/appex-sharedux ) | - | 20 | 0 | 11 | 0 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 88 | 0 | 10 | 0 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 56 | 0 | 6 | 0 | @@ -644,7 +644,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 10 | 0 | 9 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 2 | 0 | 2 | 1 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 32 | 0 | 31 | 0 | -| | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 18 | 0 | 9 | 1 | +| | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 19 | 0 | 9 | 1 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 17 | 0 | 17 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 8 | 0 | 3 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 25 | 0 | 24 | 0 | @@ -655,15 +655,15 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 24 | 0 | 24 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 27 | 0 | 26 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 5 | 0 | 3 | 1 | -| | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 26 | 0 | 11 | 0 | -| | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 17 | 0 | 16 | 0 | +| | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 34 | 0 | 14 | 1 | +| | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 24 | 0 | 23 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 2 | 0 | 1 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 14 | 0 | 13 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 1 | 0 | 1 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 2 | 0 | 0 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 15 | 0 | 4 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 16 | 0 | 6 | 0 | -| | [@elastic/obs-ux-management-team](https://github.com/orgs/elastic/teams/obs-ux-management-team) | - | 146 | 0 | 146 | 0 | +| | [@elastic/obs-ux-management-team](https://github.com/orgs/elastic/teams/obs-ux-management-team) | - | 154 | 0 | 154 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 20 | 0 | 12 | 0 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 4 | 0 | 4 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 102 | 2 | 65 | 1 | @@ -674,12 +674,12 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 14 | 0 | 8 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 137 | 5 | 105 | 2 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 2 | 0 | 1 | 0 | -| | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 26 | 0 | 10 | 0 | +| | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 27 | 0 | 11 | 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) | - | 11 | 0 | 11 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 39 | 0 | 25 | 1 | | | [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs-knowledge-team) | - | 86 | 0 | 86 | 1 | -| | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 44 | 0 | 30 | 0 | +| | [@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) | - | 55 | 0 | 46 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 7 | 0 | 6 | 0 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | Contains functionality for the unified data table which can be integrated into apps | 117 | 0 | 55 | 1 | @@ -692,7 +692,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 2 | 0 | 2 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 24 | 0 | 14 | 0 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 152 | 0 | 149 | 3 | -| | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 4 | 0 | 3 | 0 | +| | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 10 | 0 | 9 | 1 | | | [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) | - | 13 | 0 | 13 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 6 | 0 | 2 | 0 | | | [@elastic/security-detection-rule-management](https://github.com/orgs/elastic/teams/security-detection-rule-management) | - | 18 | 0 | 9 | 0 | diff --git a/api_docs/presentation_panel.mdx b/api_docs/presentation_panel.mdx index 805041209c843..b160f7e62ef3e 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-02-05 +date: 2024-02-07 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 e8c9e477b9e16..92cce48e5f8d0 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-02-05 +date: 2024-02-07 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 658bfef26ecc1..69b5facaf15c9 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-02-05 +date: 2024-02-07 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 f40a4be207e99..d7592150f1268 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-02-05 +date: 2024-02-07 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 1ca63774fa4ff..2ab200ba8cfe4 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-02-05 +date: 2024-02-07 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 86339caa48d95..7d02e444371a7 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-02-05 +date: 2024-02-07 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 68cb7010d7193..fce7892027c3a 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'rollup'] --- import rollupObj from './rollup.devdocs.json'; diff --git a/api_docs/rule_registry.devdocs.json b/api_docs/rule_registry.devdocs.json index 7a83d2485b070..f905186bb93ba 100644 --- a/api_docs/rule_registry.devdocs.json +++ b/api_docs/rule_registry.devdocs.json @@ -2059,7 +2059,15 @@ "section": "def-common.ActionGroup", "text": "ActionGroup" }, - "<\"default\">[]; defaultActionGroupId: \"default\"; category: string; producer: string; actionVariables?: { context?: ", + "<\"default\">[]; schemas?: { params?: { type: \"zod\"; schema: Zod.ZodObject | Zod.ZodIntersection; } | { type: \"config-schema\"; schema: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + "; } | undefined; } | undefined; defaultActionGroupId: \"default\"; category: string; producer: string; actionVariables?: { context?: ", { "pluginId": "@kbn/alerting-types", "scope": "common", @@ -3579,16 +3587,16 @@ "description": [], "signature": [ "(alerts: { _id: string; _source: T; }[], suppressionWindow: string, enrichAlerts?: ((alerts: { _id: string; _source: T; }[], params: { spaceId: string; }) => Promise<{ _id: string; _source: T; }[]>) | undefined, currentTimeOverride?: Date | undefined) => Promise(alerts: { _id: string; _source: T; }[], suppressionWindow: string, enrichAlerts?: ((alerts: { _id: string; _source: T; }[], params: { spaceId: string; }) => Promise<{ _id: string; _source: T; }[]>) | undefined, currentTimeOverride?: Date | undefined, isRuleExecutionOnly?: boolean | undefined) => Promise<", { "pluginId": "ruleRegistry", "scope": "server", "docId": "kibRuleRegistryPluginApi", - "section": "def-server.PersistenceAlertServiceResult", - "text": "PersistenceAlertServiceResult" + "section": "def-server.SuppressedAlertServiceResult", + "text": "SuppressedAlertServiceResult" }, - ", \"alertsWereTruncated\">>" + ">" ], "path": "x-pack/plugins/rule_registry/server/utils/persistence_types.ts", "deprecated": false, @@ -3647,6 +3655,20 @@ "path": "x-pack/plugins/rule_registry/server/utils/persistence_types.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "ruleRegistry", + "id": "def-server.PersistenceServices.alertWithSuppression.$5", + "type": "CompoundType", + "tags": [], + "label": "isRuleExecutionOnly", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "x-pack/plugins/rule_registry/server/utils/persistence_types.ts", + "deprecated": false, + "trackAdoption": false } ] } @@ -3833,6 +3855,52 @@ } ], "initialIsOpen": false + }, + { + "parentPluginId": "ruleRegistry", + "id": "def-server.SuppressedAlertServiceResult", + "type": "Interface", + "tags": [], + "label": "SuppressedAlertServiceResult", + "description": [], + "signature": [ + { + "pluginId": "ruleRegistry", + "scope": "server", + "docId": "kibRuleRegistryPluginApi", + "section": "def-server.SuppressedAlertServiceResult", + "text": "SuppressedAlertServiceResult" + }, + " extends Omit<", + { + "pluginId": "ruleRegistry", + "scope": "server", + "docId": "kibRuleRegistryPluginApi", + "section": "def-server.PersistenceAlertServiceResult", + "text": "PersistenceAlertServiceResult" + }, + ", \"alertsWereTruncated\">" + ], + "path": "x-pack/plugins/rule_registry/server/utils/persistence_types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "ruleRegistry", + "id": "def-server.SuppressedAlertServiceResult.suppressedAlerts", + "type": "Array", + "tags": [], + "label": "suppressedAlerts", + "description": [], + "signature": [ + "{ _id: string; _source: T; }[]" + ], + "path": "x-pack/plugins/rule_registry/server/utils/persistence_types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false } ], "enums": [ @@ -4355,16 +4423,16 @@ "description": [], "signature": [ "(alerts: { _id: string; _source: T; }[], suppressionWindow: string, enrichAlerts?: ((alerts: { _id: string; _source: T; }[], params: { spaceId: string; }) => Promise<{ _id: string; _source: T; }[]>) | undefined, currentTimeOverride?: Date | undefined) => Promise(alerts: { _id: string; _source: T; }[], suppressionWindow: string, enrichAlerts?: ((alerts: { _id: string; _source: T; }[], params: { spaceId: string; }) => Promise<{ _id: string; _source: T; }[]>) | undefined, currentTimeOverride?: Date | undefined, isRuleExecutionOnly?: boolean | undefined) => Promise<", { "pluginId": "ruleRegistry", "scope": "server", "docId": "kibRuleRegistryPluginApi", - "section": "def-server.PersistenceAlertServiceResult", - "text": "PersistenceAlertServiceResult" + "section": "def-server.SuppressedAlertServiceResult", + "text": "SuppressedAlertServiceResult" }, - ", \"alertsWereTruncated\">>" + ">" ], "path": "x-pack/plugins/rule_registry/server/utils/persistence_types.ts", "deprecated": false, @@ -4423,6 +4491,20 @@ "path": "x-pack/plugins/rule_registry/server/utils/persistence_types.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "ruleRegistry", + "id": "def-server.SuppressedAlertService.$5", + "type": "CompoundType", + "tags": [], + "label": "isRuleExecutionOnly", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "x-pack/plugins/rule_registry/server/utils/persistence_types.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false diff --git a/api_docs/rule_registry.mdx b/api_docs/rule_registry.mdx index e215e6d54996e..856e54fd3b9db 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ruleRegistry'] --- import ruleRegistryObj from './rule_registry.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 | |-------------------|-----------|------------------------|-----------------| -| 268 | 0 | 239 | 14 | +| 272 | 0 | 243 | 14 | ## Server diff --git a/api_docs/runtime_fields.mdx b/api_docs/runtime_fields.mdx index 4b7abfa38bbcf..3a192e41075db 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-02-05 +date: 2024-02-07 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 ef05b7471f0f6..caad57d964dd0 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-02-05 +date: 2024-02-07 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 4af3b34f9734d..8b6746c496c96 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-02-05 +date: 2024-02-07 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 d4af0e10ca07b..5856df267d933 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-02-05 +date: 2024-02-07 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 1cf23439fab3d..4c7268b2f1be4 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-02-05 +date: 2024-02-07 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 368afebe73f32..e1140371d426d 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-02-05 +date: 2024-02-07 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 463cf927ecc05..9d3c73292fb10 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-02-05 +date: 2024-02-07 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 83c48b41c7a7f..e8ff668a90b2f 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-02-05 +date: 2024-02-07 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 582f3280a80da..58bdd6065c1ee 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'screenshotting'] --- import screenshottingObj from './screenshotting.devdocs.json'; diff --git a/api_docs/security.mdx b/api_docs/security.mdx index e067a9dca2c90..840391e9b964b 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'security'] --- import securityObj from './security.devdocs.json'; diff --git a/api_docs/security_solution.devdocs.json b/api_docs/security_solution.devdocs.json index ae463270c288f..6088d1fa3033f 100644 --- a/api_docs/security_solution.devdocs.json +++ b/api_docs/security_solution.devdocs.json @@ -114,7 +114,7 @@ "label": "experimentalFeatures", "description": [], "signature": [ - "{ readonly tGridEnabled: boolean; readonly tGridEventRenderedViewEnabled: boolean; readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly chartEmbeddablesEnabled: boolean; readonly donutChartEmbeddablesEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly insightsRelatedAlertsByProcessAncestry: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly assistantStreamingEnabled: boolean; readonly socTrendsEnabled: boolean; readonly responseActionsEnabled: boolean; readonly endpointResponseActionsEnabled: boolean; readonly responseActionUploadEnabled: boolean; readonly responseActionsSentinelOneV1Enabled: boolean; readonly alertsPageChartsEnabled: boolean; readonly alertTypeEnabled: boolean; readonly expandableFlyoutInCreateRuleEnabled: boolean; readonly alertsPageFiltersEnabled: boolean; readonly assistantModelEvaluation: boolean; readonly newUserDetailsFlyout: boolean; readonly newUserDetailsFlyoutManagedUser: boolean; readonly newHostDetailsFlyout: boolean; readonly riskScoringPersistence: boolean; readonly riskScoringRoutesEnabled: boolean; readonly esqlRulesDisabled: boolean; readonly protectionUpdatesEnabled: boolean; readonly disableTimelineSaveTour: boolean; readonly riskEnginePrivilegesRouteEnabled: boolean; readonly entityAnalyticsAssetCriticalityEnabled: boolean; readonly sentinelOneDataInAnalyzerEnabled: boolean; readonly sentinelOneManualHostActionsEnabled: boolean; readonly jsonPrebuiltRulesDiffingEnabled: boolean; readonly timelineEsqlTabDisabled: boolean; }" + "{ readonly tGridEnabled: boolean; readonly tGridEventRenderedViewEnabled: boolean; readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly chartEmbeddablesEnabled: boolean; readonly donutChartEmbeddablesEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly insightsRelatedAlertsByProcessAncestry: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly assistantStreamingEnabled: boolean; readonly socTrendsEnabled: boolean; readonly responseActionsEnabled: boolean; readonly endpointResponseActionsEnabled: boolean; readonly responseActionUploadEnabled: boolean; readonly responseActionsSentinelOneV1Enabled: boolean; readonly alertsPageChartsEnabled: boolean; readonly alertTypeEnabled: boolean; readonly expandableFlyoutInCreateRuleEnabled: boolean; readonly alertsPageFiltersEnabled: boolean; readonly assistantModelEvaluation: boolean; readonly newUserDetailsFlyout: boolean; readonly newUserDetailsFlyoutManagedUser: boolean; readonly newHostDetailsFlyout: boolean; readonly riskScoringPersistence: boolean; readonly riskScoringRoutesEnabled: boolean; readonly esqlRulesDisabled: boolean; readonly protectionUpdatesEnabled: boolean; readonly disableTimelineSaveTour: boolean; readonly riskEnginePrivilegesRouteEnabled: boolean; readonly alertSuppressionForIndicatorMatchRuleEnabled: boolean; readonly entityAnalyticsAssetCriticalityEnabled: boolean; readonly sentinelOneDataInAnalyzerEnabled: boolean; readonly sentinelOneManualHostActionsEnabled: boolean; readonly jsonPrebuiltRulesDiffingEnabled: boolean; readonly timelineEsqlTabDisabled: boolean; }" ], "path": "x-pack/plugins/security_solution/public/plugin.tsx", "deprecated": false, @@ -473,7 +473,7 @@ "label": "data", "description": [], "signature": [ - "({ type: \"eql\"; id: string; name: string; actions: { id: string; params: {} & { [k: string]: unknown; }; group: string; action_type_id: string; uuid?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; }[]; tags: string[]; setup: string; enabled: boolean; revision: number; version: number; references: string[]; interval: string; query: string; description: string; risk_score: number; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; from: string; to: string; language: \"eql\"; created_at: string; created_by: string; updated_at: string; updated_by: 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; field: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; operator: \"equals\"; }[]; exceptions_list: { type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; id: string; 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; }[]; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: 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; 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; total_enrichment_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; }; status_order: number; }; } | undefined; index?: string[] | undefined; data_view_id?: string | undefined; filters?: unknown[] | undefined; event_category_override?: string | undefined; tiebreaker_field?: string | undefined; timestamp_field?: string | undefined; } | { type: \"query\"; id: string; name: string; actions: { id: string; params: {} & { [k: string]: unknown; }; group: string; action_type_id: string; uuid?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; }[]; tags: string[]; setup: string; enabled: boolean; revision: number; version: number; references: string[]; interval: string; query: string; description: string; risk_score: number; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; from: string; to: string; language: \"kuery\" | \"lucene\"; created_at: string; created_by: string; updated_at: string; updated_by: 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; field: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; operator: \"equals\"; }[]; exceptions_list: { type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; id: string; 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; }[]; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: 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; 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; total_enrichment_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; }; status_order: number; }; } | undefined; index?: string[] | undefined; filters?: unknown[] | undefined; data_view_id?: string | undefined; saved_id?: string | undefined; response_actions?: ({ params: { query?: string | undefined; ecs_mapping?: Zod.objectOutputType<{}, Zod.ZodObject<{ field: Zod.ZodOptional; value: Zod.ZodOptional]>>; }, \"strip\", Zod.ZodTypeAny, { field?: string | undefined; value?: string | string[] | undefined; }, { field?: string | undefined; value?: string | string[] | undefined; }>, \"strip\"> | undefined; queries?: { id: string; query: string; ecs_mapping?: Zod.objectOutputType<{}, Zod.ZodObject<{ field: Zod.ZodOptional; value: Zod.ZodOptional]>>; }, \"strip\", Zod.ZodTypeAny, { field?: string | undefined; value?: string | string[] | undefined; }, { field?: string | undefined; value?: string | string[] | undefined; }>, \"strip\"> | undefined; version?: string | undefined; platform?: string | undefined; removed?: boolean | undefined; snapshot?: boolean | undefined; }[] | undefined; pack_id?: string | undefined; saved_query_id?: string | undefined; timeout?: number | undefined; }; action_type_id: \".osquery\"; } | { params: { command: \"isolate\"; comment?: string | undefined; }; action_type_id: \".endpoint\"; })[] | undefined; alert_suppression?: { group_by: string[]; duration?: { value: number; unit: \"m\" | \"h\" | \"s\"; } | undefined; missing_fields_strategy?: \"doNotSuppress\" | \"suppress\" | undefined; } | undefined; } | { type: \"saved_query\"; id: string; name: string; actions: { id: string; params: {} & { [k: string]: unknown; }; group: string; action_type_id: string; uuid?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; }[]; tags: string[]; setup: string; enabled: boolean; revision: number; version: number; references: string[]; interval: string; description: string; risk_score: number; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; from: string; to: string; language: \"kuery\" | \"lucene\"; created_at: string; created_by: string; updated_at: string; updated_by: 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; field: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; operator: \"equals\"; }[]; exceptions_list: { type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; id: string; 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; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: 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; 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; total_enrichment_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; }; status_order: number; }; } | undefined; index?: string[] | undefined; query?: string | undefined; filters?: unknown[] | undefined; data_view_id?: string | undefined; response_actions?: ({ params: { query?: string | undefined; ecs_mapping?: Zod.objectOutputType<{}, Zod.ZodObject<{ field: Zod.ZodOptional; value: Zod.ZodOptional]>>; }, \"strip\", Zod.ZodTypeAny, { field?: string | undefined; value?: string | string[] | undefined; }, { field?: string | undefined; value?: string | string[] | undefined; }>, \"strip\"> | undefined; queries?: { id: string; query: string; ecs_mapping?: Zod.objectOutputType<{}, Zod.ZodObject<{ field: Zod.ZodOptional; value: Zod.ZodOptional]>>; }, \"strip\", Zod.ZodTypeAny, { field?: string | undefined; value?: string | string[] | undefined; }, { field?: string | undefined; value?: string | string[] | undefined; }>, \"strip\"> | undefined; version?: string | undefined; platform?: string | undefined; removed?: boolean | undefined; snapshot?: boolean | undefined; }[] | undefined; pack_id?: string | undefined; saved_query_id?: string | undefined; timeout?: number | undefined; }; action_type_id: \".osquery\"; } | { params: { command: \"isolate\"; comment?: string | undefined; }; action_type_id: \".endpoint\"; })[] | undefined; alert_suppression?: { group_by: string[]; duration?: { value: number; unit: \"m\" | \"h\" | \"s\"; } | undefined; missing_fields_strategy?: \"doNotSuppress\" | \"suppress\" | undefined; } | undefined; } | { type: \"threshold\"; id: string; name: string; actions: { id: string; params: {} & { [k: string]: unknown; }; group: string; action_type_id: string; uuid?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; }[]; tags: string[]; setup: string; enabled: boolean; revision: number; version: number; references: string[]; interval: string; query: string; description: string; risk_score: number; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; threshold: { value: number; field: (string | string[]) & (string | string[] | undefined); cardinality?: { value: number; field: string; }[] | undefined; }; from: string; to: string; language: \"kuery\" | \"lucene\"; created_at: string; created_by: string; updated_at: string; updated_by: 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; field: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; operator: \"equals\"; }[]; exceptions_list: { type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; id: string; 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; }[]; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: 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; 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; total_enrichment_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; }; status_order: number; }; } | undefined; index?: string[] | undefined; filters?: unknown[] | undefined; data_view_id?: string | undefined; saved_id?: string | undefined; alert_suppression?: { duration: { value: number; unit: \"m\" | \"h\" | \"s\"; }; } | undefined; } | { type: \"threat_match\"; id: string; name: string; actions: { id: string; params: {} & { [k: string]: unknown; }; group: string; action_type_id: string; uuid?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; }[]; tags: string[]; setup: string; enabled: boolean; revision: number; version: number; references: string[]; interval: string; query: string; description: string; risk_score: number; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; from: string; to: string; language: \"kuery\" | \"lucene\"; created_at: string; created_by: string; updated_at: string; updated_by: 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; field: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; operator: \"equals\"; }[]; exceptions_list: { type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; id: string; 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: { type: \"mapping\"; value: string; field: string; }[]; }[]; threat_index: string[]; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: 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; 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; total_enrichment_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; }; status_order: number; }; } | undefined; index?: string[] | undefined; filters?: unknown[] | undefined; data_view_id?: string | undefined; saved_id?: string | undefined; threat_filters?: unknown[] | undefined; threat_indicator_path?: string | undefined; threat_language?: \"lucene\" | \"kuery\" | undefined; concurrent_searches?: number | undefined; items_per_search?: number | undefined; } | { type: \"machine_learning\"; id: string; name: string; actions: { id: string; params: {} & { [k: string]: unknown; }; group: string; action_type_id: string; uuid?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; }[]; tags: string[]; setup: string; enabled: boolean; revision: number; version: number; references: string[]; interval: string; description: string; risk_score: number; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; from: string; to: string; created_at: string; created_by: string; updated_at: string; updated_by: 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; field: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; operator: \"equals\"; }[]; exceptions_list: { type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; id: string; 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[]) & (string | string[] | undefined); license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: 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; 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; total_enrichment_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; }; status_order: number; }; } | undefined; } | { type: \"new_terms\"; id: string; name: string; actions: { id: string; params: {} & { [k: string]: unknown; }; group: string; action_type_id: string; uuid?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; }[]; tags: string[]; setup: string; enabled: boolean; revision: number; version: number; references: string[]; interval: string; query: string; description: string; risk_score: number; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; from: string; to: string; language: \"kuery\" | \"lucene\"; created_at: string; created_by: string; updated_at: string; updated_by: 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; field: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; operator: \"equals\"; }[]; exceptions_list: { type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; id: string; 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; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: 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; 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; total_enrichment_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; }; status_order: number; }; } | undefined; index?: string[] | undefined; filters?: unknown[] | undefined; data_view_id?: string | undefined; } | { type: \"esql\"; id: string; name: string; actions: { id: string; params: {} & { [k: string]: unknown; }; group: string; action_type_id: string; uuid?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; }[]; tags: string[]; setup: string; enabled: boolean; revision: number; version: number; references: string[]; interval: string; query: string; description: string; risk_score: number; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; from: string; to: string; language: \"esql\"; created_at: string; created_by: string; updated_at: string; updated_by: 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; field: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; operator: \"equals\"; }[]; exceptions_list: { type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; id: string; 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; }[]; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: 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; 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; total_enrichment_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; }; status_order: number; }; } | undefined; })[]" + "({ type: \"eql\"; id: string; name: string; actions: { id: string; params: {} & { [k: string]: unknown; }; group: string; action_type_id: string; uuid?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; }[]; tags: string[]; setup: string; enabled: boolean; revision: number; version: number; references: string[]; interval: string; query: string; description: string; risk_score: number; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; from: string; to: string; language: \"eql\"; created_at: string; created_by: string; updated_at: string; updated_by: 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; field: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; operator: \"equals\"; }[]; exceptions_list: { type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; id: string; 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; }[]; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: 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; 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; total_enrichment_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; }; status_order: number; }; } | undefined; index?: string[] | undefined; data_view_id?: string | undefined; filters?: unknown[] | undefined; event_category_override?: string | undefined; tiebreaker_field?: string | undefined; timestamp_field?: string | undefined; } | { type: \"query\"; id: string; name: string; actions: { id: string; params: {} & { [k: string]: unknown; }; group: string; action_type_id: string; uuid?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; }[]; tags: string[]; setup: string; enabled: boolean; revision: number; version: number; references: string[]; interval: string; query: string; description: string; risk_score: number; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; from: string; to: string; language: \"kuery\" | \"lucene\"; created_at: string; created_by: string; updated_at: string; updated_by: 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; field: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; operator: \"equals\"; }[]; exceptions_list: { type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; id: string; 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; }[]; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: 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; 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; total_enrichment_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; }; status_order: number; }; } | undefined; index?: string[] | undefined; filters?: unknown[] | undefined; data_view_id?: string | undefined; saved_id?: string | undefined; response_actions?: ({ params: { query?: string | undefined; ecs_mapping?: Zod.objectOutputType<{}, Zod.ZodObject<{ field: Zod.ZodOptional; value: Zod.ZodOptional]>>; }, \"strip\", Zod.ZodTypeAny, { field?: string | undefined; value?: string | string[] | undefined; }, { field?: string | undefined; value?: string | string[] | undefined; }>, \"strip\"> | undefined; queries?: { id: string; query: string; ecs_mapping?: Zod.objectOutputType<{}, Zod.ZodObject<{ field: Zod.ZodOptional; value: Zod.ZodOptional]>>; }, \"strip\", Zod.ZodTypeAny, { field?: string | undefined; value?: string | string[] | undefined; }, { field?: string | undefined; value?: string | string[] | undefined; }>, \"strip\"> | undefined; version?: string | undefined; platform?: string | undefined; removed?: boolean | undefined; snapshot?: boolean | undefined; }[] | undefined; pack_id?: string | undefined; saved_query_id?: string | undefined; timeout?: number | undefined; }; action_type_id: \".osquery\"; } | { params: { command: \"isolate\"; comment?: string | undefined; }; action_type_id: \".endpoint\"; })[] | undefined; alert_suppression?: { group_by: string[]; duration?: { value: number; unit: \"m\" | \"h\" | \"s\"; } | undefined; missing_fields_strategy?: \"doNotSuppress\" | \"suppress\" | undefined; } | undefined; } | { type: \"saved_query\"; id: string; name: string; actions: { id: string; params: {} & { [k: string]: unknown; }; group: string; action_type_id: string; uuid?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; }[]; tags: string[]; setup: string; enabled: boolean; revision: number; version: number; references: string[]; interval: string; description: string; risk_score: number; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; from: string; to: string; language: \"kuery\" | \"lucene\"; created_at: string; created_by: string; updated_at: string; updated_by: 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; field: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; operator: \"equals\"; }[]; exceptions_list: { type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; id: string; 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; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: 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; 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; total_enrichment_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; }; status_order: number; }; } | undefined; index?: string[] | undefined; query?: string | undefined; filters?: unknown[] | undefined; data_view_id?: string | undefined; response_actions?: ({ params: { query?: string | undefined; ecs_mapping?: Zod.objectOutputType<{}, Zod.ZodObject<{ field: Zod.ZodOptional; value: Zod.ZodOptional]>>; }, \"strip\", Zod.ZodTypeAny, { field?: string | undefined; value?: string | string[] | undefined; }, { field?: string | undefined; value?: string | string[] | undefined; }>, \"strip\"> | undefined; queries?: { id: string; query: string; ecs_mapping?: Zod.objectOutputType<{}, Zod.ZodObject<{ field: Zod.ZodOptional; value: Zod.ZodOptional]>>; }, \"strip\", Zod.ZodTypeAny, { field?: string | undefined; value?: string | string[] | undefined; }, { field?: string | undefined; value?: string | string[] | undefined; }>, \"strip\"> | undefined; version?: string | undefined; platform?: string | undefined; removed?: boolean | undefined; snapshot?: boolean | undefined; }[] | undefined; pack_id?: string | undefined; saved_query_id?: string | undefined; timeout?: number | undefined; }; action_type_id: \".osquery\"; } | { params: { command: \"isolate\"; comment?: string | undefined; }; action_type_id: \".endpoint\"; })[] | undefined; alert_suppression?: { group_by: string[]; duration?: { value: number; unit: \"m\" | \"h\" | \"s\"; } | undefined; missing_fields_strategy?: \"doNotSuppress\" | \"suppress\" | undefined; } | undefined; } | { type: \"threshold\"; id: string; name: string; actions: { id: string; params: {} & { [k: string]: unknown; }; group: string; action_type_id: string; uuid?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; }[]; tags: string[]; setup: string; enabled: boolean; revision: number; version: number; references: string[]; interval: string; query: string; description: string; risk_score: number; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; threshold: { value: number; field: (string | string[]) & (string | string[] | undefined); cardinality?: { value: number; field: string; }[] | undefined; }; from: string; to: string; language: \"kuery\" | \"lucene\"; created_at: string; created_by: string; updated_at: string; updated_by: 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; field: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; operator: \"equals\"; }[]; exceptions_list: { type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; id: string; 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; }[]; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: 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; 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; total_enrichment_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; }; status_order: number; }; } | undefined; index?: string[] | undefined; filters?: unknown[] | undefined; data_view_id?: string | undefined; saved_id?: string | undefined; alert_suppression?: { duration: { value: number; unit: \"m\" | \"h\" | \"s\"; }; } | undefined; } | { type: \"threat_match\"; id: string; name: string; actions: { id: string; params: {} & { [k: string]: unknown; }; group: string; action_type_id: string; uuid?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; }[]; tags: string[]; setup: string; enabled: boolean; revision: number; version: number; references: string[]; interval: string; query: string; description: string; risk_score: number; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; from: string; to: string; language: \"kuery\" | \"lucene\"; created_at: string; created_by: string; updated_at: string; updated_by: 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; field: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; operator: \"equals\"; }[]; exceptions_list: { type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; id: string; 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: { type: \"mapping\"; value: string; field: string; }[]; }[]; threat_index: string[]; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: 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; 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; total_enrichment_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; }; status_order: number; }; } | undefined; index?: string[] | undefined; filters?: unknown[] | undefined; data_view_id?: string | undefined; saved_id?: string | undefined; alert_suppression?: { group_by: string[]; duration?: { value: number; unit: \"m\" | \"h\" | \"s\"; } | undefined; missing_fields_strategy?: \"doNotSuppress\" | \"suppress\" | undefined; } | undefined; threat_filters?: unknown[] | undefined; threat_indicator_path?: string | undefined; threat_language?: \"lucene\" | \"kuery\" | undefined; concurrent_searches?: number | undefined; items_per_search?: number | undefined; } | { type: \"machine_learning\"; id: string; name: string; actions: { id: string; params: {} & { [k: string]: unknown; }; group: string; action_type_id: string; uuid?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; }[]; tags: string[]; setup: string; enabled: boolean; revision: number; version: number; references: string[]; interval: string; description: string; risk_score: number; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; from: string; to: string; created_at: string; created_by: string; updated_at: string; updated_by: 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; field: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; operator: \"equals\"; }[]; exceptions_list: { type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; id: string; 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[]) & (string | string[] | undefined); license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: 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; 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; total_enrichment_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; }; status_order: number; }; } | undefined; } | { type: \"new_terms\"; id: string; name: string; actions: { id: string; params: {} & { [k: string]: unknown; }; group: string; action_type_id: string; uuid?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; }[]; tags: string[]; setup: string; enabled: boolean; revision: number; version: number; references: string[]; interval: string; query: string; description: string; risk_score: number; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; from: string; to: string; language: \"kuery\" | \"lucene\"; created_at: string; created_by: string; updated_at: string; updated_by: 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; field: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; operator: \"equals\"; }[]; exceptions_list: { type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; id: string; 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; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: 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; 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; total_enrichment_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; }; status_order: number; }; } | undefined; index?: string[] | undefined; filters?: unknown[] | undefined; data_view_id?: string | undefined; } | { type: \"esql\"; id: string; name: string; actions: { id: string; params: {} & { [k: string]: unknown; }; group: string; action_type_id: string; uuid?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; }[]; tags: string[]; setup: string; enabled: boolean; revision: number; version: number; references: string[]; interval: string; query: string; description: string; risk_score: number; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; from: string; to: string; language: \"esql\"; created_at: string; created_by: string; updated_at: string; updated_by: 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; field: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; operator: \"equals\"; }[]; exceptions_list: { type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; id: string; 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; }[]; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: 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; 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; total_enrichment_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; }; status_order: number; }; } | undefined; })[]" ], "path": "x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts", "deprecated": false, @@ -568,7 +568,7 @@ "\nExperimental flag needed to enable the link" ], "signature": [ - "\"assistantModelEvaluation\" | \"assistantStreamingEnabled\" | \"tGridEnabled\" | \"tGridEventRenderedViewEnabled\" | \"excludePoliciesInFilterEnabled\" | \"kubernetesEnabled\" | \"chartEmbeddablesEnabled\" | \"donutChartEmbeddablesEnabled\" | \"previewTelemetryUrlEnabled\" | \"insightsRelatedAlertsByProcessAncestry\" | \"extendedRuleExecutionLoggingEnabled\" | \"socTrendsEnabled\" | \"responseActionsEnabled\" | \"endpointResponseActionsEnabled\" | \"responseActionUploadEnabled\" | \"responseActionsSentinelOneV1Enabled\" | \"alertsPageChartsEnabled\" | \"alertTypeEnabled\" | \"expandableFlyoutInCreateRuleEnabled\" | \"alertsPageFiltersEnabled\" | \"newUserDetailsFlyout\" | \"newUserDetailsFlyoutManagedUser\" | \"newHostDetailsFlyout\" | \"riskScoringPersistence\" | \"riskScoringRoutesEnabled\" | \"esqlRulesDisabled\" | \"protectionUpdatesEnabled\" | \"disableTimelineSaveTour\" | \"riskEnginePrivilegesRouteEnabled\" | \"entityAnalyticsAssetCriticalityEnabled\" | \"sentinelOneDataInAnalyzerEnabled\" | \"sentinelOneManualHostActionsEnabled\" | \"jsonPrebuiltRulesDiffingEnabled\" | \"timelineEsqlTabDisabled\" | undefined" + "\"assistantModelEvaluation\" | \"assistantStreamingEnabled\" | \"tGridEnabled\" | \"tGridEventRenderedViewEnabled\" | \"excludePoliciesInFilterEnabled\" | \"kubernetesEnabled\" | \"chartEmbeddablesEnabled\" | \"donutChartEmbeddablesEnabled\" | \"previewTelemetryUrlEnabled\" | \"insightsRelatedAlertsByProcessAncestry\" | \"extendedRuleExecutionLoggingEnabled\" | \"socTrendsEnabled\" | \"responseActionsEnabled\" | \"endpointResponseActionsEnabled\" | \"responseActionUploadEnabled\" | \"responseActionsSentinelOneV1Enabled\" | \"alertsPageChartsEnabled\" | \"alertTypeEnabled\" | \"expandableFlyoutInCreateRuleEnabled\" | \"alertsPageFiltersEnabled\" | \"newUserDetailsFlyout\" | \"newUserDetailsFlyoutManagedUser\" | \"newHostDetailsFlyout\" | \"riskScoringPersistence\" | \"riskScoringRoutesEnabled\" | \"esqlRulesDisabled\" | \"protectionUpdatesEnabled\" | \"disableTimelineSaveTour\" | \"riskEnginePrivilegesRouteEnabled\" | \"alertSuppressionForIndicatorMatchRuleEnabled\" | \"entityAnalyticsAssetCriticalityEnabled\" | \"sentinelOneDataInAnalyzerEnabled\" | \"sentinelOneManualHostActionsEnabled\" | \"jsonPrebuiltRulesDiffingEnabled\" | \"timelineEsqlTabDisabled\" | undefined" ], "path": "x-pack/plugins/security_solution/public/common/links/types.ts", "deprecated": false, @@ -648,7 +648,7 @@ "\nExperimental flag needed to disable the link. Opposite of experimentalKey" ], "signature": [ - "\"assistantModelEvaluation\" | \"assistantStreamingEnabled\" | \"tGridEnabled\" | \"tGridEventRenderedViewEnabled\" | \"excludePoliciesInFilterEnabled\" | \"kubernetesEnabled\" | \"chartEmbeddablesEnabled\" | \"donutChartEmbeddablesEnabled\" | \"previewTelemetryUrlEnabled\" | \"insightsRelatedAlertsByProcessAncestry\" | \"extendedRuleExecutionLoggingEnabled\" | \"socTrendsEnabled\" | \"responseActionsEnabled\" | \"endpointResponseActionsEnabled\" | \"responseActionUploadEnabled\" | \"responseActionsSentinelOneV1Enabled\" | \"alertsPageChartsEnabled\" | \"alertTypeEnabled\" | \"expandableFlyoutInCreateRuleEnabled\" | \"alertsPageFiltersEnabled\" | \"newUserDetailsFlyout\" | \"newUserDetailsFlyoutManagedUser\" | \"newHostDetailsFlyout\" | \"riskScoringPersistence\" | \"riskScoringRoutesEnabled\" | \"esqlRulesDisabled\" | \"protectionUpdatesEnabled\" | \"disableTimelineSaveTour\" | \"riskEnginePrivilegesRouteEnabled\" | \"entityAnalyticsAssetCriticalityEnabled\" | \"sentinelOneDataInAnalyzerEnabled\" | \"sentinelOneManualHostActionsEnabled\" | \"jsonPrebuiltRulesDiffingEnabled\" | \"timelineEsqlTabDisabled\" | undefined" + "\"assistantModelEvaluation\" | \"assistantStreamingEnabled\" | \"tGridEnabled\" | \"tGridEventRenderedViewEnabled\" | \"excludePoliciesInFilterEnabled\" | \"kubernetesEnabled\" | \"chartEmbeddablesEnabled\" | \"donutChartEmbeddablesEnabled\" | \"previewTelemetryUrlEnabled\" | \"insightsRelatedAlertsByProcessAncestry\" | \"extendedRuleExecutionLoggingEnabled\" | \"socTrendsEnabled\" | \"responseActionsEnabled\" | \"endpointResponseActionsEnabled\" | \"responseActionUploadEnabled\" | \"responseActionsSentinelOneV1Enabled\" | \"alertsPageChartsEnabled\" | \"alertTypeEnabled\" | \"expandableFlyoutInCreateRuleEnabled\" | \"alertsPageFiltersEnabled\" | \"newUserDetailsFlyout\" | \"newUserDetailsFlyoutManagedUser\" | \"newHostDetailsFlyout\" | \"riskScoringPersistence\" | \"riskScoringRoutesEnabled\" | \"esqlRulesDisabled\" | \"protectionUpdatesEnabled\" | \"disableTimelineSaveTour\" | \"riskEnginePrivilegesRouteEnabled\" | \"alertSuppressionForIndicatorMatchRuleEnabled\" | \"entityAnalyticsAssetCriticalityEnabled\" | \"sentinelOneDataInAnalyzerEnabled\" | \"sentinelOneManualHostActionsEnabled\" | \"jsonPrebuiltRulesDiffingEnabled\" | \"timelineEsqlTabDisabled\" | undefined" ], "path": "x-pack/plugins/security_solution/public/common/links/types.ts", "deprecated": false, @@ -1986,7 +1986,7 @@ "label": "experimentalFeatures", "description": [], "signature": [ - "{ readonly tGridEnabled: boolean; readonly tGridEventRenderedViewEnabled: boolean; readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly chartEmbeddablesEnabled: boolean; readonly donutChartEmbeddablesEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly insightsRelatedAlertsByProcessAncestry: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly assistantStreamingEnabled: boolean; readonly socTrendsEnabled: boolean; readonly responseActionsEnabled: boolean; readonly endpointResponseActionsEnabled: boolean; readonly responseActionUploadEnabled: boolean; readonly responseActionsSentinelOneV1Enabled: boolean; readonly alertsPageChartsEnabled: boolean; readonly alertTypeEnabled: boolean; readonly expandableFlyoutInCreateRuleEnabled: boolean; readonly alertsPageFiltersEnabled: boolean; readonly assistantModelEvaluation: boolean; readonly newUserDetailsFlyout: boolean; readonly newUserDetailsFlyoutManagedUser: boolean; readonly newHostDetailsFlyout: boolean; readonly riskScoringPersistence: boolean; readonly riskScoringRoutesEnabled: boolean; readonly esqlRulesDisabled: boolean; readonly protectionUpdatesEnabled: boolean; readonly disableTimelineSaveTour: boolean; readonly riskEnginePrivilegesRouteEnabled: boolean; readonly entityAnalyticsAssetCriticalityEnabled: boolean; readonly sentinelOneDataInAnalyzerEnabled: boolean; readonly sentinelOneManualHostActionsEnabled: boolean; readonly jsonPrebuiltRulesDiffingEnabled: boolean; readonly timelineEsqlTabDisabled: boolean; }" + "{ readonly tGridEnabled: boolean; readonly tGridEventRenderedViewEnabled: boolean; readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly chartEmbeddablesEnabled: boolean; readonly donutChartEmbeddablesEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly insightsRelatedAlertsByProcessAncestry: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly assistantStreamingEnabled: boolean; readonly socTrendsEnabled: boolean; readonly responseActionsEnabled: boolean; readonly endpointResponseActionsEnabled: boolean; readonly responseActionUploadEnabled: boolean; readonly responseActionsSentinelOneV1Enabled: boolean; readonly alertsPageChartsEnabled: boolean; readonly alertTypeEnabled: boolean; readonly expandableFlyoutInCreateRuleEnabled: boolean; readonly alertsPageFiltersEnabled: boolean; readonly assistantModelEvaluation: boolean; readonly newUserDetailsFlyout: boolean; readonly newUserDetailsFlyoutManagedUser: boolean; readonly newHostDetailsFlyout: boolean; readonly riskScoringPersistence: boolean; readonly riskScoringRoutesEnabled: boolean; readonly esqlRulesDisabled: boolean; readonly protectionUpdatesEnabled: boolean; readonly disableTimelineSaveTour: boolean; readonly riskEnginePrivilegesRouteEnabled: boolean; readonly alertSuppressionForIndicatorMatchRuleEnabled: boolean; readonly entityAnalyticsAssetCriticalityEnabled: boolean; readonly sentinelOneDataInAnalyzerEnabled: boolean; readonly sentinelOneManualHostActionsEnabled: boolean; readonly jsonPrebuiltRulesDiffingEnabled: boolean; readonly timelineEsqlTabDisabled: boolean; }" ], "path": "x-pack/plugins/security_solution/public/types.ts", "deprecated": false, @@ -3030,7 +3030,7 @@ "label": "ConfigType", "description": [], "signature": [ - "Omit; }>, \"offeringSettings\"> & { experimentalFeatures: ", + "Omit; }>, \"offeringSettings\"> & { experimentalFeatures: ", { "pluginId": "securitySolution", "scope": "common", @@ -3105,7 +3105,7 @@ "\nThe security solution generic experimental features" ], "signature": [ - "{ readonly tGridEnabled: boolean; readonly tGridEventRenderedViewEnabled: boolean; readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly chartEmbeddablesEnabled: boolean; readonly donutChartEmbeddablesEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly insightsRelatedAlertsByProcessAncestry: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly assistantStreamingEnabled: boolean; readonly socTrendsEnabled: boolean; readonly responseActionsEnabled: boolean; readonly endpointResponseActionsEnabled: boolean; readonly responseActionUploadEnabled: boolean; readonly responseActionsSentinelOneV1Enabled: boolean; readonly alertsPageChartsEnabled: boolean; readonly alertTypeEnabled: boolean; readonly expandableFlyoutInCreateRuleEnabled: boolean; readonly alertsPageFiltersEnabled: boolean; readonly assistantModelEvaluation: boolean; readonly newUserDetailsFlyout: boolean; readonly newUserDetailsFlyoutManagedUser: boolean; readonly newHostDetailsFlyout: boolean; readonly riskScoringPersistence: boolean; readonly riskScoringRoutesEnabled: boolean; readonly esqlRulesDisabled: boolean; readonly protectionUpdatesEnabled: boolean; readonly disableTimelineSaveTour: boolean; readonly riskEnginePrivilegesRouteEnabled: boolean; readonly entityAnalyticsAssetCriticalityEnabled: boolean; readonly sentinelOneDataInAnalyzerEnabled: boolean; readonly sentinelOneManualHostActionsEnabled: boolean; readonly jsonPrebuiltRulesDiffingEnabled: boolean; readonly timelineEsqlTabDisabled: boolean; }" + "{ readonly tGridEnabled: boolean; readonly tGridEventRenderedViewEnabled: boolean; readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly chartEmbeddablesEnabled: boolean; readonly donutChartEmbeddablesEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly insightsRelatedAlertsByProcessAncestry: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly assistantStreamingEnabled: boolean; readonly socTrendsEnabled: boolean; readonly responseActionsEnabled: boolean; readonly endpointResponseActionsEnabled: boolean; readonly responseActionUploadEnabled: boolean; readonly responseActionsSentinelOneV1Enabled: boolean; readonly alertsPageChartsEnabled: boolean; readonly alertTypeEnabled: boolean; readonly expandableFlyoutInCreateRuleEnabled: boolean; readonly alertsPageFiltersEnabled: boolean; readonly assistantModelEvaluation: boolean; readonly newUserDetailsFlyout: boolean; readonly newUserDetailsFlyoutManagedUser: boolean; readonly newHostDetailsFlyout: boolean; readonly riskScoringPersistence: boolean; readonly riskScoringRoutesEnabled: boolean; readonly esqlRulesDisabled: boolean; readonly protectionUpdatesEnabled: boolean; readonly disableTimelineSaveTour: boolean; readonly riskEnginePrivilegesRouteEnabled: boolean; readonly alertSuppressionForIndicatorMatchRuleEnabled: boolean; readonly entityAnalyticsAssetCriticalityEnabled: boolean; readonly sentinelOneDataInAnalyzerEnabled: boolean; readonly sentinelOneManualHostActionsEnabled: boolean; readonly jsonPrebuiltRulesDiffingEnabled: boolean; readonly timelineEsqlTabDisabled: boolean; }" ], "path": "x-pack/plugins/security_solution/server/plugin_contract.ts", "deprecated": false, @@ -3281,7 +3281,7 @@ "label": "ExperimentalFeatures", "description": [], "signature": [ - "{ readonly tGridEnabled: boolean; readonly tGridEventRenderedViewEnabled: boolean; readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly chartEmbeddablesEnabled: boolean; readonly donutChartEmbeddablesEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly insightsRelatedAlertsByProcessAncestry: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly assistantStreamingEnabled: boolean; readonly socTrendsEnabled: boolean; readonly responseActionsEnabled: boolean; readonly endpointResponseActionsEnabled: boolean; readonly responseActionUploadEnabled: boolean; readonly responseActionsSentinelOneV1Enabled: boolean; readonly alertsPageChartsEnabled: boolean; readonly alertTypeEnabled: boolean; readonly expandableFlyoutInCreateRuleEnabled: boolean; readonly alertsPageFiltersEnabled: boolean; readonly assistantModelEvaluation: boolean; readonly newUserDetailsFlyout: boolean; readonly newUserDetailsFlyoutManagedUser: boolean; readonly newHostDetailsFlyout: boolean; readonly riskScoringPersistence: boolean; readonly riskScoringRoutesEnabled: boolean; readonly esqlRulesDisabled: boolean; readonly protectionUpdatesEnabled: boolean; readonly disableTimelineSaveTour: boolean; readonly riskEnginePrivilegesRouteEnabled: boolean; readonly entityAnalyticsAssetCriticalityEnabled: boolean; readonly sentinelOneDataInAnalyzerEnabled: boolean; readonly sentinelOneManualHostActionsEnabled: boolean; readonly jsonPrebuiltRulesDiffingEnabled: boolean; readonly timelineEsqlTabDisabled: boolean; }" + "{ readonly tGridEnabled: boolean; readonly tGridEventRenderedViewEnabled: boolean; readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly chartEmbeddablesEnabled: boolean; readonly donutChartEmbeddablesEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly insightsRelatedAlertsByProcessAncestry: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly assistantStreamingEnabled: boolean; readonly socTrendsEnabled: boolean; readonly responseActionsEnabled: boolean; readonly endpointResponseActionsEnabled: boolean; readonly responseActionUploadEnabled: boolean; readonly responseActionsSentinelOneV1Enabled: boolean; readonly alertsPageChartsEnabled: boolean; readonly alertTypeEnabled: boolean; readonly expandableFlyoutInCreateRuleEnabled: boolean; readonly alertsPageFiltersEnabled: boolean; readonly assistantModelEvaluation: boolean; readonly newUserDetailsFlyout: boolean; readonly newUserDetailsFlyoutManagedUser: boolean; readonly newHostDetailsFlyout: boolean; readonly riskScoringPersistence: boolean; readonly riskScoringRoutesEnabled: boolean; readonly esqlRulesDisabled: boolean; readonly protectionUpdatesEnabled: boolean; readonly disableTimelineSaveTour: boolean; readonly riskEnginePrivilegesRouteEnabled: boolean; readonly alertSuppressionForIndicatorMatchRuleEnabled: boolean; readonly entityAnalyticsAssetCriticalityEnabled: boolean; readonly sentinelOneDataInAnalyzerEnabled: boolean; readonly sentinelOneManualHostActionsEnabled: boolean; readonly jsonPrebuiltRulesDiffingEnabled: boolean; readonly timelineEsqlTabDisabled: boolean; }" ], "path": "x-pack/plugins/security_solution/common/experimental_features.ts", "deprecated": false, @@ -3330,7 +3330,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 tGridEnabled: true; readonly tGridEventRenderedViewEnabled: true; readonly excludePoliciesInFilterEnabled: false; readonly kubernetesEnabled: true; readonly chartEmbeddablesEnabled: true; readonly donutChartEmbeddablesEnabled: false; readonly previewTelemetryUrlEnabled: false; readonly insightsRelatedAlertsByProcessAncestry: true; readonly extendedRuleExecutionLoggingEnabled: false; readonly assistantStreamingEnabled: false; readonly socTrendsEnabled: false; readonly responseActionsEnabled: true; readonly endpointResponseActionsEnabled: true; readonly responseActionUploadEnabled: true; readonly responseActionsSentinelOneV1Enabled: false; readonly alertsPageChartsEnabled: true; readonly alertTypeEnabled: false; readonly expandableFlyoutInCreateRuleEnabled: true; readonly alertsPageFiltersEnabled: true; readonly assistantModelEvaluation: false; readonly newUserDetailsFlyout: false; readonly newUserDetailsFlyoutManagedUser: false; readonly newHostDetailsFlyout: false; readonly riskScoringPersistence: true; readonly riskScoringRoutesEnabled: true; readonly esqlRulesDisabled: false; readonly protectionUpdatesEnabled: true; readonly disableTimelineSaveTour: false; readonly riskEnginePrivilegesRouteEnabled: true; readonly entityAnalyticsAssetCriticalityEnabled: false; readonly sentinelOneDataInAnalyzerEnabled: false; readonly sentinelOneManualHostActionsEnabled: true; readonly jsonPrebuiltRulesDiffingEnabled: true; readonly timelineEsqlTabDisabled: false; }" + "{ readonly tGridEnabled: true; readonly tGridEventRenderedViewEnabled: true; readonly excludePoliciesInFilterEnabled: false; readonly kubernetesEnabled: true; readonly chartEmbeddablesEnabled: true; readonly donutChartEmbeddablesEnabled: false; readonly previewTelemetryUrlEnabled: false; readonly insightsRelatedAlertsByProcessAncestry: true; readonly extendedRuleExecutionLoggingEnabled: false; readonly assistantStreamingEnabled: false; readonly socTrendsEnabled: false; readonly responseActionsEnabled: true; readonly endpointResponseActionsEnabled: true; readonly responseActionUploadEnabled: true; readonly responseActionsSentinelOneV1Enabled: false; readonly alertsPageChartsEnabled: true; readonly alertTypeEnabled: false; readonly expandableFlyoutInCreateRuleEnabled: true; readonly alertsPageFiltersEnabled: true; readonly assistantModelEvaluation: false; readonly newUserDetailsFlyout: false; readonly newUserDetailsFlyoutManagedUser: false; readonly newHostDetailsFlyout: false; readonly riskScoringPersistence: true; readonly riskScoringRoutesEnabled: true; readonly esqlRulesDisabled: false; readonly protectionUpdatesEnabled: true; readonly disableTimelineSaveTour: false; readonly riskEnginePrivilegesRouteEnabled: true; readonly alertSuppressionForIndicatorMatchRuleEnabled: false; readonly entityAnalyticsAssetCriticalityEnabled: false; readonly sentinelOneDataInAnalyzerEnabled: false; readonly sentinelOneManualHostActionsEnabled: true; readonly jsonPrebuiltRulesDiffingEnabled: true; readonly timelineEsqlTabDisabled: 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 15386516e9d85..cbd71985d822b 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'securitySolution'] --- import securitySolutionObj from './security_solution.devdocs.json'; diff --git a/api_docs/security_solution_ess.mdx b/api_docs/security_solution_ess.mdx index 92288996d22d6..63f3bd08ddb3d 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-02-05 +date: 2024-02-07 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 6ff3a97c53511..d1494e047821a 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-02-05 +date: 2024-02-07 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 004d5c9f4da1b..0e198b2b7a0f2 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-02-05 +date: 2024-02-07 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 0d44ac6bac623..23ea04c7f5720 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-02-05 +date: 2024-02-07 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 295e87f526d21..6dd06f6a0bb86 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-02-05 +date: 2024-02-07 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 f80dd8a8dc539..d1bca6bdcf5cb 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-02-05 +date: 2024-02-07 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 d793c3c32e4be..0c5fde709cbde 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'share'] --- import shareObj from './share.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 | |-------------------|-----------|------------------------|-----------------| -| 119 | 0 | 60 | 10 | +| 119 | 0 | 60 | 11 | ## Client diff --git a/api_docs/snapshot_restore.mdx b/api_docs/snapshot_restore.mdx index fd1f16c7024e6..98108ace67750 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'snapshotRestore'] --- import snapshotRestoreObj from './snapshot_restore.devdocs.json'; diff --git a/api_docs/spaces.mdx b/api_docs/spaces.mdx index 9b4177ea6037f..87ffc57960093 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'spaces'] --- import spacesObj from './spaces.devdocs.json'; diff --git a/api_docs/stack_alerts.mdx b/api_docs/stack_alerts.mdx index af3f1a9d914b4..6711616b95732 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'stackAlerts'] --- import stackAlertsObj from './stack_alerts.devdocs.json'; diff --git a/api_docs/stack_connectors.mdx b/api_docs/stack_connectors.mdx index 2041de6f2ff23..d60f8893a3d5c 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'stackConnectors'] --- import stackConnectorsObj from './stack_connectors.devdocs.json'; diff --git a/api_docs/task_manager.mdx b/api_docs/task_manager.mdx index 48bdf988ba6d7..5f01b62427616 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'taskManager'] --- import taskManagerObj from './task_manager.devdocs.json'; diff --git a/api_docs/telemetry.mdx b/api_docs/telemetry.mdx index 5ce42d6dfa423..f452244c7ac53 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetry'] --- import telemetryObj from './telemetry.devdocs.json'; diff --git a/api_docs/telemetry_collection_manager.mdx b/api_docs/telemetry_collection_manager.mdx index 0b20d3fad7ed4..e751675914deb 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-02-05 +date: 2024-02-07 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 efc2465cfc0b6..600f107c7adca 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-02-05 +date: 2024-02-07 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 23c5024489c17..00e30d5a67f71 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryManagementSection'] --- import telemetryManagementSectionObj from './telemetry_management_section.devdocs.json'; diff --git a/api_docs/text_based_languages.mdx b/api_docs/text_based_languages.mdx index 5733dd60f7ee3..8d8f72de83698 100644 --- a/api_docs/text_based_languages.mdx +++ b/api_docs/text_based_languages.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/textBasedLanguages title: "textBasedLanguages" image: https://source.unsplash.com/400x175/?github description: API docs for the textBasedLanguages plugin -date: 2024-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'textBasedLanguages'] --- import textBasedLanguagesObj from './text_based_languages.devdocs.json'; diff --git a/api_docs/threat_intelligence.mdx b/api_docs/threat_intelligence.mdx index a28189ee06ba4..b40240b83567d 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'threatIntelligence'] --- import threatIntelligenceObj from './threat_intelligence.devdocs.json'; diff --git a/api_docs/timelines.mdx b/api_docs/timelines.mdx index 963693efffd80..ac0256289d207 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'timelines'] --- import timelinesObj from './timelines.devdocs.json'; diff --git a/api_docs/transform.mdx b/api_docs/transform.mdx index c069ee5e2440d..f70b80d7700d2 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'transform'] --- import transformObj from './transform.devdocs.json'; diff --git a/api_docs/triggers_actions_ui.devdocs.json b/api_docs/triggers_actions_ui.devdocs.json index 43889de19ef3d..4a9016b138d68 100644 --- a/api_docs/triggers_actions_ui.devdocs.json +++ b/api_docs/triggers_actions_ui.devdocs.json @@ -5102,7 +5102,7 @@ "label": "setRuleProperty", "description": [], "signature": [ - "(key: Prop, value: ", + "(key: Prop, value: ", "SanitizedRule", "[Prop] | null) => void" ], diff --git a/api_docs/triggers_actions_ui.mdx b/api_docs/triggers_actions_ui.mdx index 30d4d96c7cefc..428f3e914ad3b 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'triggersActionsUi'] --- import triggersActionsUiObj from './triggers_actions_ui.devdocs.json'; diff --git a/api_docs/ui_actions.devdocs.json b/api_docs/ui_actions.devdocs.json index e9f2832c93eaf..c58443f274c71 100644 --- a/api_docs/ui_actions.devdocs.json +++ b/api_docs/ui_actions.devdocs.json @@ -2156,56 +2156,6 @@ ], "initialIsOpen": false }, - { - "parentPluginId": "uiActions", - "id": "def-public.RowClickContext", - "type": "Interface", - "tags": [], - "label": "RowClickContext", - "description": [], - "path": "packages/kbn-ui-actions-browser/src/triggers/row_click_trigger.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "uiActions", - "id": "def-public.RowClickContext.embeddable", - "type": "Unknown", - "tags": [], - "label": "embeddable", - "description": [], - "signature": [ - "unknown" - ], - "path": "packages/kbn-ui-actions-browser/src/triggers/row_click_trigger.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "uiActions", - "id": "def-public.RowClickContext.data", - "type": "Object", - "tags": [], - "label": "data", - "description": [], - "signature": [ - "{ rowIndex: number; table: ", - { - "pluginId": "expressions", - "scope": "common", - "docId": "kibExpressionsPluginApi", - "section": "def-common.Datatable", - "text": "Datatable" - }, - "; columns?: string[] | undefined; }" - ], - "path": "packages/kbn-ui-actions-browser/src/triggers/row_click_trigger.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, { "parentPluginId": "uiActions", "id": "def-public.Trigger", @@ -2632,6 +2582,37 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "uiActions", + "id": "def-public.RowClickContext", + "type": "Type", + "tags": [], + "label": "RowClickContext", + "description": [], + "signature": [ + "Partial<", + { + "pluginId": "@kbn/presentation-publishing", + "scope": "common", + "docId": "kibKbnPresentationPublishingPluginApi", + "section": "def-common.EmbeddableApiContext", + "text": "EmbeddableApiContext" + }, + "> & { data: { rowIndex: number; table: ", + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.Datatable", + "text": "Datatable" + }, + "; columns?: string[] | undefined; }; }" + ], + "path": "packages/kbn-ui-actions-browser/src/triggers/row_click_trigger.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "uiActions", "id": "def-public.VISUALIZE_FIELD_TRIGGER", diff --git a/api_docs/ui_actions.mdx b/api_docs/ui_actions.mdx index b9601a550aa6a..0f9cfba95dbed 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uiActions'] --- import uiActionsObj from './ui_actions.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 | |-------------------|-----------|------------------------|-----------------| -| 149 | 0 | 103 | 9 | +| 147 | 0 | 101 | 9 | ## Client diff --git a/api_docs/ui_actions_enhanced.mdx b/api_docs/ui_actions_enhanced.mdx index 15e8df37caf2a..bc657402727de 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-02-05 +date: 2024-02-07 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 a85f8d9cda779..636e1e8a71ad4 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedDocViewer'] --- import unifiedDocViewerObj from './unified_doc_viewer.devdocs.json'; diff --git a/api_docs/unified_histogram.devdocs.json b/api_docs/unified_histogram.devdocs.json index d94fde9203eb4..a35bcb0e25586 100644 --- a/api_docs/unified_histogram.devdocs.json +++ b/api_docs/unified_histogram.devdocs.json @@ -476,7 +476,7 @@ }, " | undefined; } & Pick<", "UnifiedHistogramLayoutProps", - ", \"className\" | \"children\" | \"query\" | \"filters\" | \"columns\" | \"container\" | \"onBrushEnd\" | \"disabledActions\" | \"timeRange\" | \"services\" | \"dataView\" | \"relativeTimeRange\" | \"renderCustomChartToggleActions\" | \"onFilter\" | \"withDefaultActions\"> & ", + ", \"className\" | \"children\" | \"query\" | \"filters\" | \"columns\" | \"container\" | \"onBrushEnd\" | \"disabledActions\" | \"timeRange\" | \"services\" | \"dataView\" | \"relativeTimeRange\" | \"renderCustomChartToggleActions\" | \"onFilter\" | \"withDefaultActions\" | \"abortController\"> & ", { "pluginId": "@kbn/shared-ux-utility", "scope": "common", @@ -1260,7 +1260,7 @@ }, " | undefined; } & Pick<", "UnifiedHistogramLayoutProps", - ", \"className\" | \"children\" | \"query\" | \"filters\" | \"columns\" | \"container\" | \"onBrushEnd\" | \"disabledActions\" | \"timeRange\" | \"services\" | \"dataView\" | \"relativeTimeRange\" | \"renderCustomChartToggleActions\" | \"onFilter\" | \"withDefaultActions\">" + ", \"className\" | \"children\" | \"query\" | \"filters\" | \"columns\" | \"container\" | \"onBrushEnd\" | \"disabledActions\" | \"timeRange\" | \"services\" | \"dataView\" | \"relativeTimeRange\" | \"renderCustomChartToggleActions\" | \"onFilter\" | \"withDefaultActions\" | \"abortController\">" ], "path": "src/plugins/unified_histogram/public/container/container.tsx", "deprecated": false, diff --git a/api_docs/unified_histogram.mdx b/api_docs/unified_histogram.mdx index a3f277f5d7b61..dadf411df077c 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-02-05 +date: 2024-02-07 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 cd2fe338a8c6f..2485b27bdd804 100644 --- a/api_docs/unified_search.devdocs.json +++ b/api_docs/unified_search.devdocs.json @@ -717,7 +717,7 @@ "section": "def-common.TimeRange", "text": "TimeRange" }, - "; }) => 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\" | undefined; fillSubmitButton?: boolean | undefined; dataViewPickerComponentProps?: ", { "pluginId": "unifiedSearch", "scope": "public", diff --git a/api_docs/unified_search.mdx b/api_docs/unified_search.mdx index 2e4d4230f7582..8fa821d0bbc17 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedSearch'] --- import unifiedSearchObj from './unified_search.devdocs.json'; diff --git a/api_docs/unified_search_autocomplete.mdx b/api_docs/unified_search_autocomplete.mdx index 446b0abc15a84..9b27d641f394c 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedSearch.autocomplete'] --- import unifiedSearchAutocompleteObj from './unified_search_autocomplete.devdocs.json'; diff --git a/api_docs/uptime.mdx b/api_docs/uptime.mdx index a94c80643f069..272789745af97 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-02-05 +date: 2024-02-07 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 4ff4cc8a184fa..fafc955715cf7 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'urlForwarding'] --- import urlForwardingObj from './url_forwarding.devdocs.json'; diff --git a/api_docs/usage_collection.mdx b/api_docs/usage_collection.mdx index 7dcf5712cc5dd..8b1aaec03bc72 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'usageCollection'] --- import usageCollectionObj from './usage_collection.devdocs.json'; diff --git a/api_docs/ux.mdx b/api_docs/ux.mdx index 35b4b006c375f..e77e838c72039 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-02-05 +date: 2024-02-07 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 321b8385bd7be..822f8ed9ee93e 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-02-05 +date: 2024-02-07 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 6232f137596b7..5d28070a30b31 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-02-05 +date: 2024-02-07 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 dc97a3354e306..c546d638347cf 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-02-05 +date: 2024-02-07 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 dbcc79270641b..94f494248c617 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-02-05 +date: 2024-02-07 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 74f35b0bcdea0..c61e06c24591d 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-02-05 +date: 2024-02-07 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 bc6d7b389525c..5faa721b0b779 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-02-05 +date: 2024-02-07 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 8a1011b8f36f6..335949c6de1d9 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-02-05 +date: 2024-02-07 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 3c8fee32184d9..0d1db20335a4c 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-02-05 +date: 2024-02-07 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 ff3d38e2554e9..35bf09c113d89 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-02-05 +date: 2024-02-07 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 039e7ec3c4d2f..35458e93479c1 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-02-05 +date: 2024-02-07 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 99bc35c9bf1e2..3978b9dbad73f 100644 --- a/api_docs/visualizations.devdocs.json +++ b/api_docs/visualizations.devdocs.json @@ -5685,7 +5685,7 @@ "section": "def-common.SOWithMetadata", "text": "SOWithMetadata" }, - "; }>" + "; meta?: undefined; }>" ], "path": "src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts", "deprecated": false, @@ -5757,7 +5757,7 @@ "section": "def-common.SOWithMetadataPartial", "text": "SOWithMetadataPartial" }, - "; }>" + "; meta?: undefined; }>" ], "path": "src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts", "deprecated": false, @@ -6936,7 +6936,15 @@ "section": "def-common.ViewMode", "text": "ViewMode" }, - ">; disableTriggers: boolean; getExplicitInput: () => ", + ">; disableTriggers: boolean; savedObjectId: ", + { + "pluginId": "@kbn/presentation-publishing", + "scope": "common", + "docId": "kibKbnPresentationPublishingPluginApi", + "section": "def-common.PublishingSubject", + "text": "PublishingSubject" + }, + "; getExplicitInput: () => ", { "pluginId": "visualizations", "scope": "public", diff --git a/api_docs/visualizations.mdx b/api_docs/visualizations.mdx index d13a64b41b789..0fba29839bcf9 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-02-05 +date: 2024-02-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visualizations'] --- import visualizationsObj from './visualizations.devdocs.json'; diff --git a/config/serverless.oblt.yml b/config/serverless.oblt.yml index e30424b5355a4..e74210acbd2d9 100644 --- a/config/serverless.oblt.yml +++ b/config/serverless.oblt.yml @@ -39,7 +39,11 @@ xpack.uptime.service.tls.certificate: /mnt/elastic-internal/http-certs/tls.crt xpack.uptime.service.tls.key: /mnt/elastic-internal/http-certs/tls.key # Fleet specific configuration -xpack.fleet.internal.registry.capabilities: ['apm', 'observability'] +xpack.fleet.internal.registry.capabilities: [ + 'apm', + 'observability', + 'uptime', +] xpack.fleet.internal.registry.kibanaVersionCheckEnabled: false xpack.fleet.internal.registry.spec.max: '3.0' # Temporary until all packages implement new spec https://github.com/elastic/kibana/issues/166742 diff --git a/docs/management/advanced-options.asciidoc b/docs/management/advanced-options.asciidoc index f18b29ac3737a..41e2d6c6bb45a 100644 --- a/docs/management/advanced-options.asciidoc +++ b/docs/management/advanced-options.asciidoc @@ -457,6 +457,9 @@ preview:[] Enable the APM Trace Explorer feature, that allows you to search and [[observability-infrastructure-profiling-integration]]`observability:enableInfrastructureProfilingIntegration`:: preview:[] Enables the Profiling view in Host details within Infrastructure. +[[observability-infrastructure-hosts-custom-dashboard]]`observability:enableInfrastructureHostsCustomDashboards`:: +preview:[] Enables option to link custom dashboards in the Host Details view. + [float] [[kibana-reporting-settings]] ==== Reporting diff --git a/docs/settings/reporting-settings.asciidoc b/docs/settings/reporting-settings.asciidoc index 192a47e184c43..ab80c11139a50 100644 --- a/docs/settings/reporting-settings.asciidoc +++ b/docs/settings/reporting-settings.asciidoc @@ -232,10 +232,10 @@ 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. Defaults to `30s`. +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 need to increase the setting. +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`. ============ `xpack.reporting.csv.scroll.strategy`:: diff --git a/examples/embeddable_examples/public/migrations/migrations_embeddable_factory.ts b/examples/embeddable_examples/public/migrations/migrations_embeddable_factory.ts index fb96fbff77b35..3dea64a742161 100644 --- a/examples/embeddable_examples/public/migrations/migrations_embeddable_factory.ts +++ b/examples/embeddable_examples/public/migrations/migrations_embeddable_factory.ts @@ -62,7 +62,7 @@ export class SimpleEmbeddableFactoryDefinition public getDisplayName() { return i18n.translate('embeddableExamples.migrations.displayName', { - defaultMessage: 'hello world', + defaultMessage: 'simple migration embeddable', }); } } diff --git a/examples/embeddable_examples/public/plugin.ts b/examples/embeddable_examples/public/plugin.ts index 12e24526d2eb2..c94eef3107972 100644 --- a/examples/embeddable_examples/public/plugin.ts +++ b/examples/embeddable_examples/public/plugin.ts @@ -31,7 +31,8 @@ import { FilterDebuggerEmbeddableFactory, FilterDebuggerEmbeddableFactoryDefinition, } from './filter_debugger'; -import { registerMarkdownEditorEmbeddable } from './react_embeddables/eui_markdown_react_embeddable'; +import { registerMarkdownEditorEmbeddable } from './react_embeddables/eui_markdown/eui_markdown_react_embeddable'; +import { registerCreateEuiMarkdownAction } from './react_embeddables/eui_markdown/create_eui_markdown_action'; export interface EmbeddableExamplesSetupDependencies { embeddable: EmbeddableSetup; @@ -54,8 +55,6 @@ export interface EmbeddableExamplesStart { factories: ExampleEmbeddableFactories; } -registerMarkdownEditorEmbeddable(); - export class EmbeddableExamplesPlugin implements Plugin< @@ -71,6 +70,9 @@ export class EmbeddableExamplesPlugin core: CoreSetup, deps: EmbeddableExamplesSetupDependencies ) { + registerMarkdownEditorEmbeddable(); + registerCreateEuiMarkdownAction(deps.uiActions); + this.exampleEmbeddableFactories.getHelloWorldEmbeddableFactory = deps.embeddable.registerEmbeddableFactory( HELLO_WORLD_EMBEDDABLE, diff --git a/examples/embeddable_examples/public/react_embeddables/eui_markdown/constants.ts b/examples/embeddable_examples/public/react_embeddables/eui_markdown/constants.ts new file mode 100644 index 0000000000000..93227e28e211b --- /dev/null +++ b/examples/embeddable_examples/public/react_embeddables/eui_markdown/constants.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 const EUI_MARKDOWN_ID = 'euiMarkdown'; +export const ADD_EUI_MARKDOWN_ACTION_ID = 'create_eui_markdown'; diff --git a/examples/embeddable_examples/public/react_embeddables/eui_markdown/create_eui_markdown_action.tsx b/examples/embeddable_examples/public/react_embeddables/eui_markdown/create_eui_markdown_action.tsx new file mode 100644 index 0000000000000..ef2bb9e5bd154 --- /dev/null +++ b/examples/embeddable_examples/public/react_embeddables/eui_markdown/create_eui_markdown_action.tsx @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { i18n } from '@kbn/i18n'; +import { apiIsPresentationContainer } from '@kbn/presentation-containers'; +import { EmbeddableApiContext } from '@kbn/presentation-publishing'; +import { IncompatibleActionError, UiActionsStart } from '@kbn/ui-actions-plugin/public'; +import { ADD_EUI_MARKDOWN_ACTION_ID, EUI_MARKDOWN_ID } from './constants'; + +// ----------------------------------------------------------------------------- +// Create and register an action which allows this embeddable to be created from +// the dashboard toolbar context menu. +// ----------------------------------------------------------------------------- +export const registerCreateEuiMarkdownAction = (uiActions: UiActionsStart) => { + uiActions.registerAction({ + id: ADD_EUI_MARKDOWN_ACTION_ID, + getIconType: () => 'editorCodeBlock', + isCompatible: async ({ embeddable }) => { + return apiIsPresentationContainer(embeddable); + }, + execute: async ({ embeddable }) => { + if (!apiIsPresentationContainer(embeddable)) throw new IncompatibleActionError(); + embeddable.addNewPanel( + { + panelType: EUI_MARKDOWN_ID, + initialState: { content: '# hello world!' }, + }, + true + ); + }, + getDisplayName: () => + i18n.translate('embeddableExamples.euiMarkdownEditor.ariaLabel', { + defaultMessage: 'EUI Markdown', + }), + }); + uiActions.attachAction('ADD_PANEL_TRIGGER', ADD_EUI_MARKDOWN_ACTION_ID); +}; diff --git a/examples/embeddable_examples/public/react_embeddables/eui_markdown/eui_markdown_react_embeddable.tsx b/examples/embeddable_examples/public/react_embeddables/eui_markdown/eui_markdown_react_embeddable.tsx new file mode 100644 index 0000000000000..c699ca5799967 --- /dev/null +++ b/examples/embeddable_examples/public/react_embeddables/eui_markdown/eui_markdown_react_embeddable.tsx @@ -0,0 +1,124 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { EuiMarkdownEditor, EuiMarkdownFormat } from '@elastic/eui'; +import { css } from '@emotion/react'; +import { + initializeReactEmbeddableTitles, + initializeReactEmbeddableUuid, + ReactEmbeddableFactory, + RegisterReactEmbeddable, + registerReactEmbeddableFactory, + useReactEmbeddableApiHandle, + useReactEmbeddableUnsavedChanges, +} from '@kbn/embeddable-plugin/public'; +import { i18n } from '@kbn/i18n'; +import { useInheritedViewMode, useStateFromPublishingSubject } from '@kbn/presentation-publishing'; +import { euiThemeVars } from '@kbn/ui-theme'; +import React from 'react'; +import { BehaviorSubject } from 'rxjs'; +import { EUI_MARKDOWN_ID } from './constants'; +import { MarkdownEditorSerializedState, MarkdownEditorApi } from './types'; + +export const registerMarkdownEditorEmbeddable = () => { + const markdownEmbeddableFactory: ReactEmbeddableFactory< + MarkdownEditorSerializedState, + MarkdownEditorApi + > = { + deserializeState: (state) => { + /** + * Here we can run migrations and inject references. + */ + return state.rawState as MarkdownEditorSerializedState; + }, + getComponent: async (state, maybeId) => { + /** + * initialize state (source of truth) + */ + const uuid = initializeReactEmbeddableUuid(maybeId); + const { titlesApi, titleComparators, serializeTitles } = + initializeReactEmbeddableTitles(state); + const contentSubject = new BehaviorSubject(state.content); + + /** + * getComponent is async so you can async import the component or load a saved object here. + * the loading will be handed gracefully by the Presentation Container. + */ + + return RegisterReactEmbeddable((apiRef) => { + /** + * Unsaved changes logic is handled automatically by this hook. You only need to provide + * a subject, setter, and optional state comparator for each key in your state type. + */ + const { unsavedChanges, resetUnsavedChanges } = useReactEmbeddableUnsavedChanges( + uuid, + markdownEmbeddableFactory, + { + content: [contentSubject, (value) => contentSubject.next(value)], + ...titleComparators, + } + ); + + /** + * Publish the API. This is what gets forwarded to the Actions framework, and to whatever the + * parent of this embeddable is. + */ + const thisApi = useReactEmbeddableApiHandle( + { + ...titlesApi, + unsavedChanges, + resetUnsavedChanges, + serializeState: async () => { + return { + rawState: { + ...serializeTitles(), + content: contentSubject.getValue(), + }, + }; + }, + }, + apiRef, + uuid + ); + + // get state for rendering + const content = useStateFromPublishingSubject(contentSubject); + const viewMode = useInheritedViewMode(thisApi) ?? 'view'; + + return viewMode === 'edit' ? ( + contentSubject.next(value)} + aria-label={i18n.translate('embeddableExamples.euiMarkdownEditor.ariaLabel', { + defaultMessage: 'Dashboard markdown editor', + })} + height="full" + /> + ) : ( + + {content ?? ''} + + ); + }); + }, + }; + + /** + * Register the defined Embeddable Factory - notice that this isn't defined + * on the plugin. Instead, it's a simple imported function. I.E to register an + * embeddable, you only need the embeddable plugin in your requiredBundles + */ + registerReactEmbeddableFactory(EUI_MARKDOWN_ID, markdownEmbeddableFactory); +}; diff --git a/examples/embeddable_examples/public/react_embeddables/eui_markdown/types.ts b/examples/embeddable_examples/public/react_embeddables/eui_markdown/types.ts new file mode 100644 index 0000000000000..1e594ff61ba68 --- /dev/null +++ b/examples/embeddable_examples/public/react_embeddables/eui_markdown/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 { + DefaultEmbeddableApi, + SerializedReactEmbeddableTitles, +} from '@kbn/embeddable-plugin/public'; + +export type MarkdownEditorSerializedState = SerializedReactEmbeddableTitles & { + content: string; +}; + +export type MarkdownEditorApi = DefaultEmbeddableApi; diff --git a/examples/embeddable_examples/public/react_embeddables/eui_markdown_react_embeddable.tsx b/examples/embeddable_examples/public/react_embeddables/eui_markdown_react_embeddable.tsx deleted file mode 100644 index b525181f307a9..0000000000000 --- a/examples/embeddable_examples/public/react_embeddables/eui_markdown_react_embeddable.tsx +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { EuiMarkdownEditor, EuiMarkdownFormat } from '@elastic/eui'; -import { css } from '@emotion/react'; -import { euiThemeVars } from '@kbn/ui-theme'; -import { - ReactEmbeddableFactory, - RegisterReactEmbeddable, - registerReactEmbeddableFactory, - useReactEmbeddableApiHandle, - initializeReactEmbeddableUuid, - initializeReactEmbeddableTitles, - SerializedReactEmbeddableTitles, - DefaultEmbeddableApi, - useReactEmbeddableUnsavedChanges, -} from '@kbn/embeddable-plugin/public'; -import { i18n } from '@kbn/i18n'; -import { useInheritedViewMode, useStateFromPublishingSubject } from '@kbn/presentation-publishing'; -import React from 'react'; -import { BehaviorSubject } from 'rxjs'; - -// ----------------------------------------------------------------------------- -// Types for this embeddable -// ----------------------------------------------------------------------------- -type MarkdownEditorSerializedState = SerializedReactEmbeddableTitles & { - content: string; -}; - -type MarkdownEditorApi = DefaultEmbeddableApi; - -const type = 'euiMarkdown'; - -// ----------------------------------------------------------------------------- -// Define the Embeddable Factory -// ----------------------------------------------------------------------------- -const markdownEmbeddableFactory: ReactEmbeddableFactory< - MarkdownEditorSerializedState, - MarkdownEditorApi -> = { - // ----------------------------------------------------------------------------- - // Deserialize function - // ----------------------------------------------------------------------------- - deserializeState: (state) => { - // We could run migrations here. - // We should inject references here. References are given as state.references - - return state.rawState as MarkdownEditorSerializedState; - }, - - // ----------------------------------------------------------------------------- - // Register the Embeddable component - // ----------------------------------------------------------------------------- - getComponent: async (state, maybeId) => { - /** - * initialize state (source of truth) - */ - const uuid = initializeReactEmbeddableUuid(maybeId); - const { titlesApi, titleComparators, serializeTitles } = initializeReactEmbeddableTitles(state); - const contentSubject = new BehaviorSubject(state.content); - - /** - * getComponent is async so you can async import the component or load a saved object here. - * the loading will be handed gracefully by the Presentation Container. - */ - - return RegisterReactEmbeddable((apiRef) => { - /** - * Unsaved changes logic is handled automatically by this hook. You only need to provide - * a subject, setter, and optional state comparator for each key in your state type. - */ - const { unsavedChanges, resetUnsavedChanges } = useReactEmbeddableUnsavedChanges( - uuid, - markdownEmbeddableFactory, - { - content: [contentSubject, (value) => contentSubject.next(value)], - ...titleComparators, - } - ); - - /** - * Publish the API. This is what gets forwarded to the Actions framework, and to whatever the - * parent of this embeddable is. - */ - const thisApi = useReactEmbeddableApiHandle( - { - ...titlesApi, - unsavedChanges, - resetUnsavedChanges, - serializeState: async () => { - return { - rawState: { - ...serializeTitles(), - content: contentSubject.getValue(), - }, - }; - }, - }, - apiRef, - uuid - ); - - // get state for rendering - const content = useStateFromPublishingSubject(contentSubject); - const viewMode = useInheritedViewMode(thisApi) ?? 'view'; - - return viewMode === 'edit' ? ( - contentSubject.next(value)} - aria-label={i18n.translate('dashboard.test.markdownEditor.ariaLabel', { - defaultMessage: 'Dashboard markdown editor', - })} - height="full" - /> - ) : ( - - {content ?? ''} - - ); - }); - }, -}; - -// ----------------------------------------------------------------------------- -// Register the defined Embeddable Factory - notice that this isn't defined -// on the plugin. Instead, it's a simple imported function. I.E to register an -// Embeddable, you only need the embeddable plugin in your requiredBundles -// ----------------------------------------------------------------------------- -export const registerMarkdownEditorEmbeddable = () => - registerReactEmbeddableFactory(type, markdownEmbeddableFactory); diff --git a/examples/embeddable_examples/tsconfig.json b/examples/embeddable_examples/tsconfig.json index 0f22cccc94483..35f799c8c4e3a 100644 --- a/examples/embeddable_examples/tsconfig.json +++ b/examples/embeddable_examples/tsconfig.json @@ -20,6 +20,7 @@ "@kbn/presentation-publishing", "@kbn/ui-theme", "@kbn/i18n", - "@kbn/es-query" + "@kbn/es-query", + "@kbn/presentation-containers" ] } diff --git a/fleet_packages.json b/fleet_packages.json index c21024e7e6688..79722187a1b0d 100644 --- a/fleet_packages.json +++ b/fleet_packages.json @@ -24,7 +24,7 @@ [ { "name": "apm", - "version": "8.13.0-preview-1705349439", + "version": "8.13.0-preview-1705349440", "forceAlignStackVersion": true, "allowSyncToPrerelease": true }, diff --git a/package.json b/package.json index 79392e212bf81..bc5428356bc68 100644 --- a/package.json +++ b/package.json @@ -376,6 +376,7 @@ "@kbn/data-view-field-editor-example-plugin": "link:examples/data_view_field_editor_example", "@kbn/data-view-field-editor-plugin": "link:src/plugins/data_view_field_editor", "@kbn/data-view-management-plugin": "link:src/plugins/data_view_management", + "@kbn/data-view-utils": "link:packages/kbn-data-view-utils", "@kbn/data-views-plugin": "link:src/plugins/data_views", "@kbn/data-visualizer-plugin": "link:x-pack/plugins/data_visualizer", "@kbn/dataset-quality-plugin": "link:x-pack/plugins/dataset_quality", @@ -1064,7 +1065,6 @@ "react-popper-tooltip": "^3.1.1", "react-redux": "^7.2.8", "react-resizable": "^3.0.4", - "react-resize-detector": "^7.1.1", "react-reverse-portal": "^2.1.0", "react-router": "^5.3.4", "react-router-config": "^5.1.1", @@ -1683,7 +1683,8 @@ "xml-crypto": "^5.0.0", "xmlbuilder": "13.0.2", "yargs": "^15.4.1", - "yarn-deduplicate": "^6.0.2" + "yarn-deduplicate": "^6.0.2", + "zod-to-json-schema": "^3.22.3" }, "packageManager": "yarn@1.22.21" } \ No newline at end of file diff --git a/packages/analytics/shippers/fullstory/src/fullstory_shipper.ts b/packages/analytics/shippers/fullstory/src/fullstory_shipper.ts index a4ad730f87b2e..d02e87cace6b1 100644 --- a/packages/analytics/shippers/fullstory/src/fullstory_shipper.ts +++ b/packages/analytics/shippers/fullstory/src/fullstory_shipper.ts @@ -30,7 +30,7 @@ const PAGE_VARS_KEYS = [ // Deployment-specific keys 'version', // x4, split to version_major, version_minor, version_patch for easier filtering - 'buildNum', // May be useful for Serverless + 'buildNum', // May be useful for Serverless, TODO: replace with buildHash 'cloudId', 'deploymentId', 'projectId', // projectId and deploymentId are mutually exclusive. They shouldn't be sent in the same offering. diff --git a/packages/core/apps/core-apps-server-internal/src/bundle_routes/register_bundle_routes.test.ts b/packages/core/apps/core-apps-server-internal/src/bundle_routes/register_bundle_routes.test.ts index e6550f6e86cb6..8a0ae599150fd 100644 --- a/packages/core/apps/core-apps-server-internal/src/bundle_routes/register_bundle_routes.test.ts +++ b/packages/core/apps/core-apps-server-internal/src/bundle_routes/register_bundle_routes.test.ts @@ -13,10 +13,12 @@ import { httpServiceMock } from '@kbn/core-http-server-mocks'; import type { InternalPluginInfo, UiPlugins } from '@kbn/core-plugins-base-server-internal'; import { registerBundleRoutes } from './register_bundle_routes'; import { FileHashCache } from './file_hash_cache'; +import { BasePath, StaticAssets } from '@kbn/core-http-server-internal'; const createPackageInfo = (parts: Partial = {}): PackageInfo => ({ buildNum: 42, - buildSha: 'sha', + buildSha: 'shasha', + buildShaShort: 'sha', dist: true, branch: 'master', version: '8.0.0', @@ -41,9 +43,12 @@ const createUiPlugins = (...ids: string[]): UiPlugins => ({ describe('registerBundleRoutes', () => { let router: ReturnType; + let staticAssets: StaticAssets; beforeEach(() => { router = httpServiceMock.createRouter(); + const basePath = httpServiceMock.createBasePath('/server-base-path') as unknown as BasePath; + staticAssets = new StaticAssets({ basePath, cdnConfig: {} as any, shaDigest: 'sha' }); }); afterEach(() => { @@ -53,7 +58,7 @@ describe('registerBundleRoutes', () => { it('registers core and shared-dep bundles', () => { registerBundleRoutes({ router, - serverBasePath: '/server-base-path', + staticAssets, packageInfo: createPackageInfo(), uiPlugins: createUiPlugins(), }); @@ -64,39 +69,39 @@ describe('registerBundleRoutes', () => { fileHashCache: expect.any(FileHashCache), isDist: true, bundlesPath: 'uiSharedDepsSrcDistDir', - publicPath: '/server-base-path/42/bundles/kbn-ui-shared-deps-src/', - routePath: '/42/bundles/kbn-ui-shared-deps-src/', + publicPath: '/server-base-path/sha/bundles/kbn-ui-shared-deps-src/', + routePath: '/sha/bundles/kbn-ui-shared-deps-src/', }); expect(registerRouteForBundleMock).toHaveBeenCalledWith(router, { fileHashCache: expect.any(FileHashCache), isDist: true, bundlesPath: 'uiSharedDepsNpmDistDir', - publicPath: '/server-base-path/42/bundles/kbn-ui-shared-deps-npm/', - routePath: '/42/bundles/kbn-ui-shared-deps-npm/', + publicPath: '/server-base-path/sha/bundles/kbn-ui-shared-deps-npm/', + routePath: '/sha/bundles/kbn-ui-shared-deps-npm/', }); expect(registerRouteForBundleMock).toHaveBeenCalledWith(router, { fileHashCache: expect.any(FileHashCache), isDist: true, bundlesPath: expect.stringMatching(/\/@kbn\/core\/target\/public$/), - publicPath: '/server-base-path/42/bundles/core/', - routePath: '/42/bundles/core/', + publicPath: '/server-base-path/sha/bundles/core/', + routePath: '/sha/bundles/core/', }); expect(registerRouteForBundleMock).toHaveBeenCalledWith(router, { fileHashCache: expect.any(FileHashCache), isDist: true, bundlesPath: 'kbnMonacoBundleDir', - publicPath: '/server-base-path/42/bundles/kbn-monaco/', - routePath: '/42/bundles/kbn-monaco/', + publicPath: '/server-base-path/sha/bundles/kbn-monaco/', + routePath: '/sha/bundles/kbn-monaco/', }); }); it('registers plugin bundles', () => { registerBundleRoutes({ router, - serverBasePath: '/server-base-path', + staticAssets, packageInfo: createPackageInfo(), uiPlugins: createUiPlugins('plugin-a', 'plugin-b'), }); @@ -107,16 +112,16 @@ describe('registerBundleRoutes', () => { fileHashCache: expect.any(FileHashCache), isDist: true, bundlesPath: '/plugins/plugin-a/public-target-dir', - publicPath: '/server-base-path/42/bundles/plugin/plugin-a/8.0.0/', - routePath: '/42/bundles/plugin/plugin-a/8.0.0/', + publicPath: '/server-base-path/sha/bundles/plugin/plugin-a/8.0.0/', + routePath: '/sha/bundles/plugin/plugin-a/8.0.0/', }); expect(registerRouteForBundleMock).toHaveBeenCalledWith(router, { fileHashCache: expect.any(FileHashCache), isDist: true, bundlesPath: '/plugins/plugin-b/public-target-dir', - publicPath: '/server-base-path/42/bundles/plugin/plugin-b/8.0.0/', - routePath: '/42/bundles/plugin/plugin-b/8.0.0/', + publicPath: '/server-base-path/sha/bundles/plugin/plugin-b/8.0.0/', + routePath: '/sha/bundles/plugin/plugin-b/8.0.0/', }); }); }); diff --git a/packages/core/apps/core-apps-server-internal/src/bundle_routes/register_bundle_routes.ts b/packages/core/apps/core-apps-server-internal/src/bundle_routes/register_bundle_routes.ts index 22266e97355e3..617f085c8ad43 100644 --- a/packages/core/apps/core-apps-server-internal/src/bundle_routes/register_bundle_routes.ts +++ b/packages/core/apps/core-apps-server-internal/src/bundle_routes/register_bundle_routes.ts @@ -13,6 +13,7 @@ import { distDir as UiSharedDepsSrcDistDir } from '@kbn/ui-shared-deps-src'; import * as KbnMonaco from '@kbn/monaco/server'; import type { IRouter } from '@kbn/core-http-server'; import type { UiPlugins } from '@kbn/core-plugins-base-server-internal'; +import { InternalStaticAssets } from '@kbn/core-http-server-internal'; import { FileHashCache } from './file_hash_cache'; import { registerRouteForBundle } from './bundles_route'; @@ -28,56 +29,61 @@ import { registerRouteForBundle } from './bundles_route'; */ export function registerBundleRoutes({ router, - serverBasePath, uiPlugins, packageInfo, + staticAssets, }: { router: IRouter; - serverBasePath: string; uiPlugins: UiPlugins; packageInfo: PackageInfo; + staticAssets: InternalStaticAssets; }) { - const { dist: isDist, buildNum } = packageInfo; + const { dist: isDist } = packageInfo; // rather than calculate the fileHash on every request, we // provide a cache object to `resolveDynamicAssetResponse()` that // will store the most recently used hashes. const fileHashCache = new FileHashCache(); + const sharedNpmDepsPath = '/bundles/kbn-ui-shared-deps-npm/'; registerRouteForBundle(router, { - publicPath: `${serverBasePath}/${buildNum}/bundles/kbn-ui-shared-deps-npm/`, - routePath: `/${buildNum}/bundles/kbn-ui-shared-deps-npm/`, + publicPath: staticAssets.prependPublicUrl(sharedNpmDepsPath) + '/', + routePath: staticAssets.prependServerPath(sharedNpmDepsPath) + '/', bundlesPath: UiSharedDepsNpm.distDir, fileHashCache, isDist, }); + const sharedDepsPath = '/bundles/kbn-ui-shared-deps-src/'; registerRouteForBundle(router, { - publicPath: `${serverBasePath}/${buildNum}/bundles/kbn-ui-shared-deps-src/`, - routePath: `/${buildNum}/bundles/kbn-ui-shared-deps-src/`, + publicPath: staticAssets.prependPublicUrl(sharedDepsPath) + '/', + routePath: staticAssets.prependServerPath(sharedDepsPath) + '/', bundlesPath: UiSharedDepsSrcDistDir, fileHashCache, isDist, }); + const coreBundlePath = '/bundles/core/'; registerRouteForBundle(router, { - publicPath: `${serverBasePath}/${buildNum}/bundles/core/`, - routePath: `/${buildNum}/bundles/core/`, + publicPath: staticAssets.prependPublicUrl(coreBundlePath) + '/', + routePath: staticAssets.prependServerPath(coreBundlePath) + '/', bundlesPath: isDist ? fromRoot('node_modules/@kbn/core/target/public') : fromRoot('src/core/target/public'), fileHashCache, isDist, }); + const monacoEditorPath = '/bundles/kbn-monaco/'; registerRouteForBundle(router, { - publicPath: `${serverBasePath}/${buildNum}/bundles/kbn-monaco/`, - routePath: `/${buildNum}/bundles/kbn-monaco/`, + publicPath: staticAssets.prependPublicUrl(monacoEditorPath) + '/', + routePath: staticAssets.prependServerPath(monacoEditorPath) + '/', bundlesPath: KbnMonaco.bundleDir, fileHashCache, isDist, }); [...uiPlugins.internal.entries()].forEach(([id, { publicTargetDir, version }]) => { + const pluginBundlesPath = `/bundles/plugin/${id}/${version}/`; registerRouteForBundle(router, { - publicPath: `${serverBasePath}/${buildNum}/bundles/plugin/${id}/${version}/`, - routePath: `/${buildNum}/bundles/plugin/${id}/${version}/`, + publicPath: staticAssets.prependPublicUrl(pluginBundlesPath) + '/', + routePath: staticAssets.prependServerPath(pluginBundlesPath) + '/', bundlesPath: publicTargetDir, fileHashCache, isDist, 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 851f443cd3e1c..f0bde326b7b74 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 @@ -16,8 +16,8 @@ import { httpResourcesMock } from '@kbn/core-http-resources-server-mocks'; import { PluginType } from '@kbn/core-base-common'; import type { RequestHandlerContext } from '@kbn/core-http-request-handler-context-server'; import { coreInternalLifecycleMock } from '@kbn/core-lifecycle-server-mocks'; -import { CoreAppsService } from './core_app'; import { of } from 'rxjs'; +import { CoreAppsService } from './core_app'; const emptyPlugins = (): UiPlugins => ({ internal: new Map(), @@ -146,7 +146,7 @@ describe('CoreApp', () => { uiPlugins: prebootUIPlugins, router: expect.any(Object), packageInfo: coreContext.env.packageInfo, - serverBasePath: internalCorePreboot.http.basePath.serverBasePath, + staticAssets: expect.any(Object), }); }); @@ -245,7 +245,23 @@ describe('CoreApp', () => { uiPlugins, router: expect.any(Object), packageInfo: coreContext.env.packageInfo, - serverBasePath: internalCoreSetup.http.basePath.serverBasePath, + staticAssets: expect.any(Object), }); }); + + it('registers SHA-scoped and non-SHA-scoped UI bundle routes', async () => { + const uiPlugins = emptyPlugins(); + internalCoreSetup.http.staticAssets.prependServerPath.mockReturnValue('/some-path'); + await coreApp.setup(internalCoreSetup, uiPlugins); + + expect(internalCoreSetup.http.registerStaticDir).toHaveBeenCalledTimes(2); + expect(internalCoreSetup.http.registerStaticDir).toHaveBeenCalledWith( + '/some-path', + expect.any(String) + ); + expect(internalCoreSetup.http.registerStaticDir).toHaveBeenCalledWith( + '/ui/{path*}', + expect.any(String) + ); + }); }); 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 3de295874d3fe..1e54d7d8aaa26 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 @@ -22,6 +22,7 @@ import type { 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 { InternalStaticAssets } from '@kbn/core-http-server-internal'; import { firstValueFrom, map, type Observable } from 'rxjs'; import { CoreAppConfig, type CoreAppConfigType, CoreAppPath } from './core_app_config'; import { registerBundleRoutes } from './bundle_routes'; @@ -33,6 +34,7 @@ interface CommonRoutesParams { httpResources: HttpResources; basePath: IBasePath; uiPlugins: UiPlugins; + staticAssets: InternalStaticAssets; onResourceNotFound: ( req: KibanaRequest, res: HttpResourcesServiceToolkit & KibanaResponseFactory @@ -77,10 +79,11 @@ export class CoreAppsService { this.registerCommonDefaultRoutes({ basePath: corePreboot.http.basePath, httpResources: corePreboot.httpResources.createRegistrar(router), + staticAssets: corePreboot.http.staticAssets, router, uiPlugins, onResourceNotFound: async (req, res) => - // THe API consumers might call various Kibana APIs (e.g. `/api/status`) when Kibana is still at the preboot + // The API consumers might call various Kibana APIs (e.g. `/api/status`) when Kibana is still at the preboot // stage, and the main HTTP server that registers API handlers isn't up yet. At this stage we don't know if // the API endpoint exists or not, and hence cannot reply with `404`. We also should not reply with completely // unexpected response (`200 text/html` for the Core app). The only suitable option is to reply with `503` @@ -125,6 +128,7 @@ export class CoreAppsService { this.registerCommonDefaultRoutes({ basePath: coreSetup.http.basePath, httpResources: resources, + staticAssets: coreSetup.http.staticAssets, router, uiPlugins, onResourceNotFound: async (req, res) => res.notFound(), @@ -210,6 +214,7 @@ export class CoreAppsService { private registerCommonDefaultRoutes({ router, basePath, + staticAssets, uiPlugins, onResourceNotFound, httpResources, @@ -259,17 +264,23 @@ export class CoreAppsService { registerBundleRoutes({ router, uiPlugins, + staticAssets, packageInfo: this.env.packageInfo, - serverBasePath: basePath.serverBasePath, }); } // After the package is built and bootstrap extracts files to bazel-bin, // assets are exposed at the root of the package and in the package's node_modules dir private registerStaticDirs(core: InternalCoreSetup | InternalCorePreboot) { - core.http.registerStaticDir( - '/ui/{path*}', - fromRoot('node_modules/@kbn/core-apps-server-internal/assets') - ); + /** + * Serve UI from sha-scoped and not-sha-scoped paths to allow time for plugin code to migrate + * Eventually we only want to serve from the sha scoped path + */ + [core.http.staticAssets.prependServerPath('/ui/{path*}'), '/ui/{path*}'].forEach((path) => { + core.http.registerStaticDir( + path, + fromRoot('node_modules/@kbn/core-apps-server-internal/assets') + ); + }); } } diff --git a/packages/core/apps/core-apps-server-internal/tsconfig.json b/packages/core/apps/core-apps-server-internal/tsconfig.json index 36ecc68c7cbc1..fc8aa9f25349c 100644 --- a/packages/core/apps/core-apps-server-internal/tsconfig.json +++ b/packages/core/apps/core-apps-server-internal/tsconfig.json @@ -32,6 +32,7 @@ "@kbn/core-lifecycle-server-mocks", "@kbn/core-ui-settings-server", "@kbn/monaco", + "@kbn/core-http-server-internal", ], "exclude": [ "target/**/*", diff --git a/packages/core/base/core-base-browser-mocks/src/core_context.mock.ts b/packages/core/base/core-base-browser-mocks/src/core_context.mock.ts index 53933d4146df3..cdbafc09c5c03 100644 --- a/packages/core/base/core-base-browser-mocks/src/core_context.mock.ts +++ b/packages/core/base/core-base-browser-mocks/src/core_context.mock.ts @@ -24,6 +24,7 @@ function createCoreContext({ production = false }: { production?: boolean } = {} branch: 'branch', buildNum: 100, buildSha: 'buildSha', + buildShaShort: 'buildShaShort', dist: false, buildDate: new Date('2023-05-15T23:12:09.000Z'), buildFlavor: 'traditional', diff --git a/packages/core/chrome/core-chrome-browser-internal/src/ui/header/header_action_menu.test.tsx b/packages/core/chrome/core-chrome-browser-internal/src/ui/header/header_action_menu.test.tsx index 9a7b4fa5a8b14..359439f3e4cdf 100644 --- a/packages/core/chrome/core-chrome-browser-internal/src/ui/header/header_action_menu.test.tsx +++ b/packages/core/chrome/core-chrome-browser-internal/src/ui/header/header_action_menu.test.tsx @@ -147,4 +147,24 @@ describe('HeaderActionMenu', () => { expect(unmounts.FOO).toHaveBeenCalledTimes(1); expect(unmounts.BAR).not.toHaveBeenCalled(); }); + + it('calls mount point `unmount` when unmounts', () => { + const TestComponent = () => { + const mounter = useHeaderActionMenuMounter(menuMount$); + return ; + }; + component = mount(); + + act(() => { + menuMount$.next(createMountPoint('FOO')); + }); + refresh(); + + expect(Object.keys(unmounts)).toEqual(['FOO']); + expect(unmounts.FOO).not.toHaveBeenCalled(); + + component.unmount(); + + expect(unmounts.FOO).toHaveBeenCalledTimes(1); + }); }); diff --git a/packages/core/chrome/core-chrome-browser-internal/src/ui/header/header_action_menu.tsx b/packages/core/chrome/core-chrome-browser-internal/src/ui/header/header_action_menu.tsx index ec2a06266a0fe..91d2077b28ea9 100644 --- a/packages/core/chrome/core-chrome-browser-internal/src/ui/header/header_action_menu.tsx +++ b/packages/core/chrome/core-chrome-browser-internal/src/ui/header/header_action_menu.tsx @@ -39,11 +39,6 @@ export const HeaderActionMenu: FC = ({ mounter }) => { const unmountRef = useRef(null); useLayoutEffect(() => { - if (unmountRef.current) { - unmountRef.current(); - unmountRef.current = null; - } - if (mounter.mount && elementRef.current) { try { unmountRef.current = mounter.mount(elementRef.current); @@ -53,6 +48,12 @@ export const HeaderActionMenu: FC = ({ mounter }) => { console.error(e); } } + return () => { + if (unmountRef.current) { + unmountRef.current(); + unmountRef.current = null; + } + }; }, [mounter]); return
; diff --git a/packages/core/http/core-http-server-internal/index.ts b/packages/core/http/core-http-server-internal/index.ts index 2867b5d2a0317..b9d4edd8a8628 100644 --- a/packages/core/http/core-http-server-internal/index.ts +++ b/packages/core/http/core-http-server-internal/index.ts @@ -17,6 +17,7 @@ export type { InternalHttpServiceStart, } from './src/types'; export { BasePath } from './src/base_path_service'; +export { type InternalStaticAssets, StaticAssets } from './src/static_assets'; export { cspConfig, CspConfig, type CspConfigType } from './src/csp'; diff --git a/packages/core/http/core-http-server-internal/src/cdn.test.ts b/packages/core/http/core-http-server-internal/src/cdn_config.test.ts similarity index 98% rename from packages/core/http/core-http-server-internal/src/cdn.test.ts rename to packages/core/http/core-http-server-internal/src/cdn_config.test.ts index 74f165bfd0f22..b6a954782f523 100644 --- a/packages/core/http/core-http-server-internal/src/cdn.test.ts +++ b/packages/core/http/core-http-server-internal/src/cdn_config.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { CdnConfig } from './cdn'; +import { CdnConfig } from './cdn_config'; describe('CdnConfig', () => { it.each([ diff --git a/packages/core/http/core-http-server-internal/src/cdn.ts b/packages/core/http/core-http-server-internal/src/cdn_config.ts similarity index 90% rename from packages/core/http/core-http-server-internal/src/cdn.ts rename to packages/core/http/core-http-server-internal/src/cdn_config.ts index 0f9b386b09237..e6fa29200ac74 100644 --- a/packages/core/http/core-http-server-internal/src/cdn.ts +++ b/packages/core/http/core-http-server-internal/src/cdn_config.ts @@ -14,15 +14,15 @@ export interface Input { } export class CdnConfig { - private url: undefined | URL; + private readonly url: undefined | URL; constructor(url?: string) { if (url) { - this.url = new URL(url); // This will throw for invalid URLs + this.url = new URL(url); // This will throw for invalid URLs, although should be validated before reaching this point } } public get host(): undefined | string { - return this.url?.host ?? undefined; + return this.url?.host; } public get baseHref(): undefined | string { diff --git a/packages/core/http/core-http-server-internal/src/http_config.test.ts b/packages/core/http/core-http-server-internal/src/http_config.test.ts index 28abe6513ced6..f0bd5773b40b9 100644 --- a/packages/core/http/core-http-server-internal/src/http_config.test.ts +++ b/packages/core/http/core-http-server-internal/src/http_config.test.ts @@ -16,8 +16,8 @@ const invalidHostnames = ['asdf$%^', '0']; let mockHostname = 'kibana-hostname'; -jest.mock('os', () => { - const original = jest.requireActual('os'); +jest.mock('node:os', () => { + const original = jest.requireActual('node:os'); return { ...original, @@ -530,6 +530,29 @@ describe('restrictInternalApis', () => { }); }); +describe('cdn', () => { + it('allows correct URL', () => { + expect(config.schema.validate({ cdn: { url: 'https://cdn.example.com' } })).toMatchObject({ + cdn: { url: 'https://cdn.example.com' }, + }); + }); + it.each([['foo'], ['http:./']])('throws for invalid URL %s', (url) => { + expect(() => config.schema.validate({ cdn: { url } })).toThrowErrorMatchingInlineSnapshot( + `"[cdn.url]: expected URI with scheme [http|https]."` + ); + }); + it.each([ + ['https://cdn.example.com:1234/asd?thing=1', 'URL query string not allowed'], + ['https://cdn.example.com:1234/asd#cool', 'URL fragment not allowed'], + [ + 'https://cdn.example.com:1234/asd?thing=1#cool', + 'URL fragment not allowed, but found "#cool"\nURL query string not allowed, but found "?thing=1"', + ], + ])('throws for disallowed values %s', (url, expecterError) => { + expect(() => config.schema.validate({ cdn: { url } })).toThrow(expecterError); + }); +}); + describe('HttpConfig', () => { it('converts customResponseHeaders to strings or arrays of strings', () => { const httpSchema = config.schema; diff --git a/packages/core/http/core-http-server-internal/src/http_config.ts b/packages/core/http/core-http-server-internal/src/http_config.ts index f6880c38e49ba..54b8e808f675b 100644 --- a/packages/core/http/core-http-server-internal/src/http_config.ts +++ b/packages/core/http/core-http-server-internal/src/http_config.ts @@ -12,8 +12,8 @@ import type { ServiceConfigDescriptor } from '@kbn/core-base-server-internal'; import { uuidRegexp } from '@kbn/core-base-server-internal'; import type { ICspConfig, IExternalUrlConfig } from '@kbn/core-http-server'; -import { hostname } from 'os'; -import url from 'url'; +import { hostname, EOL } from 'node:os'; +import url, { URL } from 'node:url'; import type { Duration } from 'moment'; import type { IHttpEluMonitorConfig } from '@kbn/core-http-server/src/elu_monitor'; @@ -24,7 +24,7 @@ import { securityResponseHeadersSchema, parseRawSecurityResponseHeadersConfig, } from './security_response_headers_config'; -import { CdnConfig } from './cdn'; +import { CdnConfig } from './cdn_config'; const validBasePathRegex = /^\/.*[^\/]$/; @@ -40,6 +40,24 @@ const validHostName = () => { return hostname().replace(/[^\x00-\x7F]/g, ''); }; +/** + * We assume the URL does not contain anything after the pathname so that + * we can safely append values to the pathname at runtime. + */ +function validateCdnURL(urlString: string): undefined | string { + const cdnURL = new URL(urlString); + const errors: string[] = []; + if (cdnURL.hash.length) { + errors.push(`URL fragment not allowed, but found "${cdnURL.hash}"`); + } + if (cdnURL.search.length) { + errors.push(`URL query string not allowed, but found "${cdnURL.search}"`); + } + if (errors.length) { + return `CDN URL "${cdnURL.href}" is invalid:${EOL}${errors.join(EOL)}`; + } +} + const configSchema = schema.object( { name: schema.string({ defaultValue: () => validHostName() }), @@ -60,7 +78,7 @@ const configSchema = schema.object( }, }), cdn: schema.object({ - url: schema.maybe(schema.uri({ scheme: ['http', 'https'] })), + url: schema.maybe(schema.uri({ scheme: ['http', 'https'], validate: validateCdnURL })), }), cors: schema.object( { diff --git a/packages/core/http/core-http-server-internal/src/http_server.test.ts b/packages/core/http/core-http-server-internal/src/http_server.test.ts index 8f5ae22612b45..129efea82cc8c 100644 --- a/packages/core/http/core-http-server-internal/src/http_server.test.ts +++ b/packages/core/http/core-http-server-internal/src/http_server.test.ts @@ -30,6 +30,7 @@ import { Readable } from 'stream'; import { KBN_CERT_PATH, KBN_KEY_PATH } from '@kbn/dev-utils'; import moment from 'moment'; import { of, Observable, BehaviorSubject } from 'rxjs'; +import { mockCoreContext } from '@kbn/core-base-server-mocks'; const routerOptions: RouterOptions = { isDev: false, @@ -54,8 +55,9 @@ let config$: Observable; let configWithSSL: HttpConfig; let configWithSSL$: Observable; -const loggingService = loggingSystemMock.create(); -const logger = loggingService.get(); +const coreContext = mockCoreContext.create(); +const loggingService = coreContext.logger; +const logger = coreContext.logger.get(); const enhanceWithContext = (fn: (...args: any[]) => any) => fn.bind(null, {}); let certificate: string; @@ -99,7 +101,7 @@ beforeEach(() => { } as HttpConfig; configWithSSL$ = of(configWithSSL); - server = new HttpServer(loggingService, 'tests', of(config.shutdownTimeout)); + server = new HttpServer(coreContext, 'tests', of(config.shutdownTimeout)); }); afterEach(async () => { diff --git a/packages/core/http/core-http-server-internal/src/http_server.ts b/packages/core/http/core-http-server-internal/src/http_server.ts index 1361c64bb67ce..ae9025d5cd9a7 100644 --- a/packages/core/http/core-http-server-internal/src/http_server.ts +++ b/packages/core/http/core-http-server-internal/src/http_server.ts @@ -48,6 +48,8 @@ import { performance } from 'perf_hooks'; import { isBoom } from '@hapi/boom'; import { identity } from 'lodash'; import { IHttpEluMonitorConfig } from '@kbn/core-http-server/src/elu_monitor'; +import { Env } from '@kbn/config'; +import { CoreContext } from '@kbn/core-base-server-internal'; import { HttpConfig } from './http_config'; import { adoptToHapiAuthFormat } from './lifecycle/auth'; import { adoptToHapiOnPreAuth } from './lifecycle/on_pre_auth'; @@ -178,15 +180,20 @@ export class HttpServer { private stopped = false; private readonly log: Logger; + private readonly logger: LoggerFactory; private readonly authState: AuthStateStorage; private readonly authRequestHeaders: AuthHeadersStorage; private readonly authResponseHeaders: AuthHeadersStorage; + private readonly env: Env; constructor( - private readonly logger: LoggerFactory, + private readonly coreContext: CoreContext, private readonly name: string, private readonly shutdownTimeout$: Observable ) { + const { logger, env } = this.coreContext; + this.logger = logger; + this.env = env; this.authState = new AuthStateStorage(() => this.authRegistered); this.authRequestHeaders = new AuthHeadersStorage(); this.authResponseHeaders = new AuthHeadersStorage(); @@ -269,7 +276,11 @@ export class HttpServer { this.setupResponseLogging(); this.setupGracefulShutdownHandlers(); - const staticAssets = new StaticAssets(basePathService, config.cdn); + const staticAssets = new StaticAssets({ + basePath: basePathService, + cdnConfig: config.cdn, + shaDigest: this.env.packageInfo.buildShaShort, + }); return { registerRouter: this.registerRouter.bind(this), diff --git a/packages/core/http/core-http-server-internal/src/http_service.ts b/packages/core/http/core-http-server-internal/src/http_service.ts index 1ce15eb06b231..3dcab5be510e9 100644 --- a/packages/core/http/core-http-server-internal/src/http_service.ts +++ b/packages/core/http/core-http-server-internal/src/http_service.ts @@ -76,8 +76,8 @@ export class HttpService configService.atPath(externalUrlConfig.path), ]).pipe(map(([http, csp, externalUrl]) => new HttpConfig(http, csp, externalUrl))); const shutdownTimeout$ = this.config$.pipe(map(({ shutdownTimeout }) => shutdownTimeout)); - this.prebootServer = new HttpServer(logger, 'Preboot', shutdownTimeout$); - this.httpServer = new HttpServer(logger, 'Kibana', shutdownTimeout$); + this.prebootServer = new HttpServer(coreContext, 'Preboot', shutdownTimeout$); + this.httpServer = new HttpServer(coreContext, 'Kibana', shutdownTimeout$); this.httpsRedirectServer = new HttpsRedirectServer(logger.get('http', 'redirect', 'server')); } diff --git a/packages/core/http/core-http-server-internal/src/static_assets.test.ts b/packages/core/http/core-http-server-internal/src/static_assets.test.ts deleted file mode 100644 index 0b1acd4e73fd9..0000000000000 --- a/packages/core/http/core-http-server-internal/src/static_assets.test.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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { StaticAssets } from './static_assets'; -import { BasePath } from './base_path_service'; -import { CdnConfig } from './cdn'; - -describe('StaticAssets', () => { - let basePath: BasePath; - let cdnConfig: CdnConfig; - let staticAssets: StaticAssets; - - beforeEach(() => { - basePath = new BasePath('/base-path'); - }); - - describe('#getHrefBase()', () => { - it('provides fallback to server base path', () => { - cdnConfig = CdnConfig.from(); - staticAssets = new StaticAssets(basePath, cdnConfig); - expect(staticAssets.getHrefBase()).toEqual('/base-path'); - }); - - it('provides the correct HREF given a CDN is configured', () => { - cdnConfig = CdnConfig.from({ url: 'https://cdn.example.com/test' }); - staticAssets = new StaticAssets(basePath, cdnConfig); - expect(staticAssets.getHrefBase()).toEqual('https://cdn.example.com/test'); - }); - }); - - describe('#getPluginAssetHref()', () => { - it('returns the expected value when CDN config is not set', () => { - cdnConfig = CdnConfig.from(); - staticAssets = new StaticAssets(basePath, cdnConfig); - expect(staticAssets.getPluginAssetHref('foo', 'path/to/img.gif')).toEqual( - '/base-path/plugins/foo/assets/path/to/img.gif' - ); - }); - - it('returns the expected value when CDN config is set', () => { - cdnConfig = CdnConfig.from({ url: 'https://cdn.example.com/test' }); - staticAssets = new StaticAssets(basePath, cdnConfig); - expect(staticAssets.getPluginAssetHref('bar', 'path/to/img.gif')).toEqual( - 'https://cdn.example.com/test/plugins/bar/assets/path/to/img.gif' - ); - }); - - it('removes leading slash from the', () => { - cdnConfig = CdnConfig.from(); - staticAssets = new StaticAssets(basePath, cdnConfig); - expect(staticAssets.getPluginAssetHref('dolly', '/path/for/something.svg')).toEqual( - '/base-path/plugins/dolly/assets/path/for/something.svg' - ); - }); - }); -}); diff --git a/packages/core/http/core-http-server-internal/src/static_assets.ts b/packages/core/http/core-http-server-internal/src/static_assets.ts deleted file mode 100644 index 4dfe46d8d31a6..0000000000000 --- a/packages/core/http/core-http-server-internal/src/static_assets.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 and the Server Side Public License, v 1; you may not 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 { BasePath } from './base_path_service'; -import { CdnConfig } from './cdn'; - -export interface InternalStaticAssets { - getHrefBase(): string; - getPluginAssetHref(pluginName: string, assetPath: string): string; -} - -export class StaticAssets implements InternalStaticAssets { - private readonly assetsHrefBase: string; - - constructor(basePath: BasePath, cdnConfig: CdnConfig) { - const hrefToUse = cdnConfig.baseHref ?? basePath.serverBasePath; - this.assetsHrefBase = hrefToUse.endsWith('/') ? hrefToUse.slice(0, -1) : hrefToUse; - } - - /** - * Returns a href (hypertext reference) intended to be used as the base for constructing - * other hrefs to static assets. - */ - getHrefBase(): string { - return this.assetsHrefBase; - } - - getPluginAssetHref(pluginName: string, assetPath: string): string { - if (assetPath.startsWith('/')) { - assetPath = assetPath.slice(1); - } - return `${this.assetsHrefBase}/plugins/${pluginName}/assets/${assetPath}`; - } -} diff --git a/packages/core/http/core-http-server-internal/src/static_assets/index.ts b/packages/core/http/core-http-server-internal/src/static_assets/index.ts new file mode 100644 index 0000000000000..1f4dc880583c3 --- /dev/null +++ b/packages/core/http/core-http-server-internal/src/static_assets/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 { type InternalStaticAssets, StaticAssets } from './static_assets'; diff --git a/packages/core/http/core-http-server-internal/src/static_assets/static_assets.test.ts b/packages/core/http/core-http-server-internal/src/static_assets/static_assets.test.ts new file mode 100644 index 0000000000000..438a87765d85e --- /dev/null +++ b/packages/core/http/core-http-server-internal/src/static_assets/static_assets.test.ts @@ -0,0 +1,132 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { StaticAssets, type StaticAssetsParams } from './static_assets'; +import { BasePath } from '../base_path_service'; +import { CdnConfig } from '../cdn_config'; + +describe('StaticAssets', () => { + let basePath: BasePath; + let cdnConfig: CdnConfig; + let staticAssets: StaticAssets; + let args: StaticAssetsParams; + + beforeEach(() => { + basePath = new BasePath('/base-path'); + cdnConfig = CdnConfig.from(); + args = { basePath, cdnConfig, shaDigest: '' }; + }); + + describe('#getHrefBase()', () => { + it('provides fallback to server base path', () => { + staticAssets = new StaticAssets(args); + expect(staticAssets.getHrefBase()).toEqual('/base-path'); + }); + + it('provides the correct HREF given a CDN is configured', () => { + args.cdnConfig = CdnConfig.from({ url: 'https://cdn.example.com/test' }); + staticAssets = new StaticAssets(args); + expect(staticAssets.getHrefBase()).toEqual('https://cdn.example.com/test'); + }); + }); + + describe('#getPluginAssetHref()', () => { + it('returns the expected value when CDN is not configured', () => { + staticAssets = new StaticAssets(args); + expect(staticAssets.getPluginAssetHref('foo', 'path/to/img.gif')).toEqual( + '/base-path/plugins/foo/assets/path/to/img.gif' + ); + }); + + it('returns the expected value when CDN is configured', () => { + args.cdnConfig = CdnConfig.from({ url: 'https://cdn.example.com/test' }); + staticAssets = new StaticAssets(args); + expect(staticAssets.getPluginAssetHref('bar', 'path/to/img.gif')).toEqual( + 'https://cdn.example.com/test/plugins/bar/assets/path/to/img.gif' + ); + }); + + it('removes leading and trailing slash from the assetPath', () => { + staticAssets = new StaticAssets(args); + expect(staticAssets.getPluginAssetHref('dolly', '/path/for/something.svg/')).toEqual( + '/base-path/plugins/dolly/assets/path/for/something.svg' + ); + }); + it('removes leading and trailing slash from the assetPath when CDN is configured', () => { + args.cdnConfig = CdnConfig.from({ url: 'https://cdn.example.com/test' }); + staticAssets = new StaticAssets(args); + expect(staticAssets.getPluginAssetHref('dolly', '/path/for/something.svg/')).toEqual( + 'https://cdn.example.com/test/plugins/dolly/assets/path/for/something.svg' + ); + }); + }); + + describe('with a SHA digest provided', () => { + describe('cdn', () => { + it.each([ + ['https://cdn.example.com', 'https://cdn.example.com/beef', undefined], + ['https://cdn.example.com:1234', 'https://cdn.example.com:1234/beef', undefined], + [ + 'https://cdn.example.com:1234/roast', + 'https://cdn.example.com:1234/roast/beef', + undefined, + ], + // put slashes around shaDigest + [ + 'https://cdn.example.com:1234/roast-slash', + 'https://cdn.example.com:1234/roast-slash/beef', + '/beef/', + ], + ])('suffixes the digest to the CDNs path value (%s)', (url, expectedHref, shaDigest) => { + args.shaDigest = shaDigest ?? 'beef'; + args.cdnConfig = CdnConfig.from({ url }); + staticAssets = new StaticAssets(args); + expect(staticAssets.getHrefBase()).toEqual(expectedHref); + }); + }); + + describe('base path', () => { + it.each([ + ['', '/beef', undefined], + ['/', '/beef', undefined], + ['/roast', '/roast/beef', undefined], + ['/roast/', '/roast/beef', '/beef/'], // cheeky test adding a slashes to digest + ])('suffixes the digest to the server base path "%s")', (url, expectedPath, shaDigest) => { + basePath = new BasePath(url); + args.basePath = basePath; + args.shaDigest = shaDigest ?? 'beef'; + staticAssets = new StaticAssets(args); + expect(staticAssets.getHrefBase()).toEqual(expectedPath); + }); + }); + }); + + describe('#getPluginServerPath()', () => { + it('provides the path plugin assets can use for server routes', () => { + args.shaDigest = '1234'; + staticAssets = new StaticAssets(args); + expect(staticAssets.getPluginServerPath('myPlugin', '/fun/times')).toEqual( + '/1234/plugins/myPlugin/assets/fun/times' + ); + }); + }); + describe('#prependPublicUrl()', () => { + it('with a CDN it appends as expected', () => { + args.cdnConfig = CdnConfig.from({ url: 'http://cdn.example.com/cool?123=true' }); + staticAssets = new StaticAssets(args); + expect(staticAssets.prependPublicUrl('beans')).toEqual( + 'http://cdn.example.com/cool/beans?123=true' + ); + }); + + it('without a CDN it appends as expected', () => { + staticAssets = new StaticAssets(args); + expect(staticAssets.prependPublicUrl('/cool/beans')).toEqual('/base-path/cool/beans'); + }); + }); +}); diff --git a/packages/core/http/core-http-server-internal/src/static_assets/static_assets.ts b/packages/core/http/core-http-server-internal/src/static_assets/static_assets.ts new file mode 100644 index 0000000000000..f5f7d7ac80430 --- /dev/null +++ b/packages/core/http/core-http-server-internal/src/static_assets/static_assets.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 type { BasePath } from '../base_path_service'; +import { CdnConfig } from '../cdn_config'; +import { + suffixPathnameToPathname, + suffixPathnameToURLPathname, + removeSurroundingSlashes, +} from './util'; + +export interface InternalStaticAssets { + getHrefBase(): string; + /** + * Intended for use by server code rendering UI or generating links to static assets + * that will ultimately be called from the browser and must respect settings like + * serverBasePath + */ + getPluginAssetHref(pluginName: string, assetPath: string): string; + /** + * Intended for use by server code wanting to register static assets against Kibana + * as server paths + */ + getPluginServerPath(pluginName: string, assetPath: string): string; + /** + * Similar to getPluginServerPath, but not plugin-scoped + */ + prependServerPath(pathname: string): string; + + /** + * Will append the given path segment to the configured public path. + * + * @note This could return a path or full URL depending on whether a CDN is configured. + */ + prependPublicUrl(pathname: string): string; +} + +/** @internal */ +export interface StaticAssetsParams { + basePath: BasePath; + cdnConfig: CdnConfig; + shaDigest: string; +} + +/** + * Convention is for trailing slashes in pathnames are stripped. + */ +export class StaticAssets implements InternalStaticAssets { + private readonly assetsHrefBase: string; + private readonly assetsServerPathBase: string; + private readonly hasCdnHost: boolean; + + constructor({ basePath, cdnConfig, shaDigest }: StaticAssetsParams) { + const cdnBaseHref = cdnConfig.baseHref; + if (cdnBaseHref) { + this.hasCdnHost = true; + this.assetsHrefBase = suffixPathnameToURLPathname(cdnBaseHref, shaDigest); + } else { + this.hasCdnHost = false; + this.assetsHrefBase = suffixPathnameToPathname(basePath.serverBasePath, shaDigest); + } + this.assetsServerPathBase = `/${shaDigest}`; + } + + /** + * Returns a href (hypertext reference) intended to be used as the base for constructing + * other hrefs to static assets. + */ + public getHrefBase(): string { + return this.assetsHrefBase; + } + + public getPluginAssetHref(pluginName: string, assetPath: string): string { + if (assetPath.startsWith('/')) { + assetPath = assetPath.slice(1); + } + return `${this.assetsHrefBase}/plugins/${pluginName}/assets/${removeSurroundingSlashes( + assetPath + )}`; + } + + public prependServerPath(path: string): string { + return `${this.assetsServerPathBase}/${removeSurroundingSlashes(path)}`; + } + + public prependPublicUrl(pathname: string): string { + if (this.hasCdnHost) { + return suffixPathnameToURLPathname(this.assetsHrefBase, pathname); + } + return suffixPathnameToPathname(this.assetsHrefBase, pathname); + } + + public getPluginServerPath(pluginName: string, assetPath: string): string { + return `${this.assetsServerPathBase}/plugins/${pluginName}/assets/${removeSurroundingSlashes( + assetPath + )}`; + } +} diff --git a/packages/core/http/core-http-server-internal/src/static_assets/util.ts b/packages/core/http/core-http-server-internal/src/static_assets/util.ts new file mode 100644 index 0000000000000..c86f56fc239a2 --- /dev/null +++ b/packages/core/http/core-http-server-internal/src/static_assets/util.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 { URL, format } from 'node:url'; + +function isEmptyPathname(pathname: string): boolean { + return !pathname || pathname === '/'; +} + +function removeTailSlashes(pathname: string): string { + return pathname.replace(/\/+$/, ''); +} + +function removeLeadSlashes(pathname: string): string { + return pathname.replace(/^\/+/, ''); +} + +export function removeSurroundingSlashes(pathname: string): string { + return removeLeadSlashes(removeTailSlashes(pathname)); +} + +export function suffixPathnameToURLPathname(urlString: string, pathname: string): string { + const url = new URL(urlString); + url.pathname = suffixPathnameToPathname(url.pathname, pathname); + return format(url); +} + +/** + * Appends a value to pathname. Pathname is assumed to come from URL.pathname + * Also do some quality control on the path to ensure that it matches URL.pathname. + */ +export function suffixPathnameToPathname(pathnameA: string, pathnameB: string): string { + if (isEmptyPathname(pathnameA)) { + return `/${removeSurroundingSlashes(pathnameB)}`; + } + if (isEmptyPathname(pathnameB)) { + return `/${removeSurroundingSlashes(pathnameA)}`; + } + return `/${removeSurroundingSlashes(pathnameA)}/${removeSurroundingSlashes(pathnameB)}`; +} diff --git a/packages/core/http/core-http-server-internal/tsconfig.json b/packages/core/http/core-http-server-internal/tsconfig.json index e163741c21c7e..7c52ff584a532 100644 --- a/packages/core/http/core-http-server-internal/tsconfig.json +++ b/packages/core/http/core-http-server-internal/tsconfig.json @@ -33,6 +33,7 @@ "@kbn/core-execution-context-server-mocks", "@kbn/core-http-context-server-mocks", "@kbn/logging-mocks", + "@kbn/core-base-server-mocks", ], "exclude": [ "target/**/*", diff --git a/packages/core/http/core-http-server-mocks/src/http_service.mock.ts b/packages/core/http/core-http-server-mocks/src/http_service.mock.ts index f8ecfadfeb87e..7172accf98a9f 100644 --- a/packages/core/http/core-http-server-mocks/src/http_service.mock.ts +++ b/packages/core/http/core-http-server-mocks/src/http_service.mock.ts @@ -87,8 +87,11 @@ const createInternalStaticAssetsMock = ( basePath: BasePathMocked, cdnUrl: undefined | string = undefined ): InternalStaticAssetsMocked => ({ - getHrefBase: jest.fn(() => cdnUrl ?? basePath.serverBasePath), + getHrefBase: jest.fn().mockReturnValue(cdnUrl ?? basePath.serverBasePath), getPluginAssetHref: jest.fn().mockReturnValue(cdnUrl ?? basePath.serverBasePath), + getPluginServerPath: jest.fn((v, _) => v), + prependServerPath: jest.fn((v) => v), + prependPublicUrl: jest.fn((v) => v), }); const createAuthMock = () => { @@ -212,6 +215,7 @@ const createSetupContractMock = < getServerInfo: internalMock.getServerInfo, staticAssets: { getPluginAssetHref: jest.fn().mockImplementation((assetPath: string) => assetPath), + prependPublicUrl: jest.fn().mockImplementation((pathname: string) => pathname), }, }; @@ -227,6 +231,7 @@ const createStartContractMock = () => { getServerInfo: jest.fn(), staticAssets: { getPluginAssetHref: jest.fn().mockImplementation((assetPath: string) => assetPath), + prependPublicUrl: jest.fn().mockImplementation((pathname: string) => pathname), }, }; diff --git a/packages/core/http/core-http-server/src/static_assets.ts b/packages/core/http/core-http-server/src/static_assets.ts index c0cc8597d1540..a839beae8023e 100644 --- a/packages/core/http/core-http-server/src/static_assets.ts +++ b/packages/core/http/core-http-server/src/static_assets.ts @@ -23,4 +23,23 @@ export interface IStaticAssets { * ``` */ getPluginAssetHref(assetPath: string): string; + + /** + * Will return an href, either a path for or full URL with the provided path + * appended to the static assets public base path. + * + * Useful for instances were you need to render your own HTML page and link to + * certain static assets. + * + * @example + * ```ts + * // I want to retrieve the href for Kibana's favicon, requires knowledge of path: + * const favIconHref = core.http.statisAssets.prependPublicUrl('/ui/favicons/favicon.svg'); + * ``` + * + * @note Only use this if you know what you are doing and there is no other option. + * This creates a strong coupling between asset dir structure and your code. + * @param pathname + */ + prependPublicUrl(pathname: string): string; } diff --git a/packages/core/plugins/core-plugins-browser-internal/src/test_helpers/mocks.ts b/packages/core/plugins/core-plugins-browser-internal/src/test_helpers/mocks.ts index 787a7d7678194..38b86ed553cf2 100644 --- a/packages/core/plugins/core-plugins-browser-internal/src/test_helpers/mocks.ts +++ b/packages/core/plugins/core-plugins-browser-internal/src/test_helpers/mocks.ts @@ -23,6 +23,7 @@ export const createPluginInitializerContextMock = (config: unknown = {}) => { branch: 'branch', buildNum: 100, buildSha: 'buildSha', + buildShaShort: 'buildShaShort', dist: false, buildDate: new Date('2023-05-15T23:12:09.000Z'), buildFlavor: 'traditional', diff --git a/packages/core/plugins/core-plugins-browser-mocks/src/plugins_service.mock.ts b/packages/core/plugins/core-plugins-browser-mocks/src/plugins_service.mock.ts index ff3d60c5c5706..6a52194d520f2 100644 --- a/packages/core/plugins/core-plugins-browser-mocks/src/plugins_service.mock.ts +++ b/packages/core/plugins/core-plugins-browser-mocks/src/plugins_service.mock.ts @@ -49,6 +49,7 @@ const createPluginInitializerContextMock = ( branch: 'branch', buildNum: 100, buildSha: 'buildSha', + buildShaShort: 'buildShaShort', dist: false, buildDate: new Date('2023-05-15T23:12:09.000Z'), buildFlavor, diff --git a/packages/core/plugins/core-plugins-server-internal/src/discovery/plugin_manifest_parser.test.ts b/packages/core/plugins/core-plugins-server-internal/src/discovery/plugin_manifest_parser.test.ts index 202cef2ca09d4..cdcf7ac7063f3 100644 --- a/packages/core/plugins/core-plugins-server-internal/src/discovery/plugin_manifest_parser.test.ts +++ b/packages/core/plugins/core-plugins-server-internal/src/discovery/plugin_manifest_parser.test.ts @@ -19,6 +19,7 @@ const packageInfo: PackageInfo = { branch: 'master', buildNum: 1, buildSha: '', + buildShaShort: '', version: '7.0.0-alpha1', dist: false, buildDate: new Date('2023-05-15T23:12:09.000Z'), diff --git a/packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts b/packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts index c3ed7f6a433b9..3cb8777d647d1 100644 --- a/packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts +++ b/packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts @@ -236,6 +236,7 @@ export function createPluginSetupContext({ registerOnPreResponse: deps.http.registerOnPreResponse, basePath: deps.http.basePath, staticAssets: { + prependPublicUrl: (pathname: string) => deps.http.staticAssets.prependPublicUrl(pathname), getPluginAssetHref: (assetPath: string) => deps.http.staticAssets.getPluginAssetHref(plugin.name, assetPath), }, @@ -329,6 +330,7 @@ export function createPluginStartContext({ basePath: deps.http.basePath, getServerInfo: deps.http.getServerInfo, staticAssets: { + prependPublicUrl: (pathname: string) => deps.http.staticAssets.prependPublicUrl(pathname), getPluginAssetHref: (assetPath: string) => deps.http.staticAssets.getPluginAssetHref(plugin.name, assetPath), }, diff --git a/packages/core/plugins/core-plugins-server-internal/src/plugins_service.test.ts b/packages/core/plugins/core-plugins-server-internal/src/plugins_service.test.ts index 2185bfe13fb19..e25a1b924e420 100644 --- a/packages/core/plugins/core-plugins-server-internal/src/plugins_service.test.ts +++ b/packages/core/plugins/core-plugins-server-internal/src/plugins_service.test.ts @@ -1165,8 +1165,10 @@ describe('PluginsService', () => { }); describe('plugin initialization', () => { + let prebootPlugins: PluginWrapper[]; + let standardPlugins: PluginWrapper[]; beforeEach(() => { - const prebootPlugins = [ + prebootPlugins = [ createPlugin('plugin-1-preboot', { type: PluginType.preboot, path: 'path-1-preboot', @@ -1178,7 +1180,7 @@ describe('PluginsService', () => { version: 'version-2', }), ]; - const standardPlugins = [ + standardPlugins = [ createPlugin('plugin-1-standard', { path: 'path-1-standard', version: 'version-1', @@ -1299,6 +1301,31 @@ describe('PluginsService', () => { expect(standardMockPluginSystem.setupPlugins).not.toHaveBeenCalled(); }); + it('#preboot registers expected static dirs', async () => { + prebootDeps.http.staticAssets.getPluginServerPath.mockImplementation( + (pluginName: string) => `/static-assets/${pluginName}` + ); + await pluginsService.discover({ environment: environmentPreboot, node: nodePreboot }); + await pluginsService.preboot(prebootDeps); + expect(prebootDeps.http.registerStaticDir).toHaveBeenCalledTimes(prebootPlugins.length * 2); + expect(prebootDeps.http.registerStaticDir).toHaveBeenCalledWith( + '/static-assets/plugin-1-preboot', + expect.any(String) + ); + expect(prebootDeps.http.registerStaticDir).toHaveBeenCalledWith( + '/plugins/plugin-1-preboot/assets/{path*}', + expect.any(String) + ); + expect(prebootDeps.http.registerStaticDir).toHaveBeenCalledWith( + '/static-assets/plugin-2-preboot', + expect.any(String) + ); + expect(prebootDeps.http.registerStaticDir).toHaveBeenCalledWith( + '/plugins/plugin-2-preboot/assets/{path*}', + expect.any(String) + ); + }); + it('#setup does initialize `standard` plugins if plugins.initialize is true', async () => { config$.next({ plugins: { initialize: true } }); await pluginsService.discover({ environment: environmentPreboot, node: nodePreboot }); @@ -1319,6 +1346,32 @@ describe('PluginsService', () => { expect(prebootMockPluginSystem.setupPlugins).not.toHaveBeenCalled(); expect(initialized).toBe(false); }); + + it('#setup registers expected static dirs', async () => { + await pluginsService.discover({ environment: environmentPreboot, node: nodePreboot }); + await pluginsService.preboot(prebootDeps); + setupDeps.http.staticAssets.getPluginServerPath.mockImplementation( + (pluginName: string) => `/static-assets/${pluginName}` + ); + await pluginsService.setup(setupDeps); + expect(setupDeps.http.registerStaticDir).toHaveBeenCalledTimes(standardPlugins.length * 2); + expect(setupDeps.http.registerStaticDir).toHaveBeenCalledWith( + '/static-assets/plugin-1-standard', + expect.any(String) + ); + expect(setupDeps.http.registerStaticDir).toHaveBeenCalledWith( + '/plugins/plugin-1-standard/assets/{path*}', + expect.any(String) + ); + expect(setupDeps.http.registerStaticDir).toHaveBeenCalledWith( + '/static-assets/plugin-2-standard', + expect.any(String) + ); + expect(setupDeps.http.registerStaticDir).toHaveBeenCalledWith( + '/plugins/plugin-2-standard/assets/{path*}', + expect.any(String) + ); + }); }); describe('#getExposedPluginConfigsToUsage', () => { diff --git a/packages/core/plugins/core-plugins-server-internal/src/plugins_service.ts b/packages/core/plugins/core-plugins-server-internal/src/plugins_service.ts index a5f7bfaef7d73..da5d77d8be675 100644 --- a/packages/core/plugins/core-plugins-server-internal/src/plugins_service.ts +++ b/packages/core/plugins/core-plugins-server-internal/src/plugins_service.ts @@ -448,10 +448,16 @@ export class PluginsService uiPluginInternalInfo: Map ) { for (const [pluginName, pluginInfo] of uiPluginInternalInfo) { - deps.http.registerStaticDir( + /** + * Serve UI from sha-scoped and not-sha-scoped paths to allow time for plugin code to migrate + * Eventually we only want to serve from the sha scoped path + */ + [ + deps.http.staticAssets.getPluginServerPath(pluginName, '{path*}'), `/plugins/${pluginName}/assets/{path*}`, - pluginInfo.publicAssetsDir - ); + ].forEach((path) => { + deps.http.registerStaticDir(path, pluginInfo.publicAssetsDir); + }); } } } diff --git a/packages/core/rendering/core-rendering-server-internal/src/__snapshots__/rendering_service.test.ts.snap b/packages/core/rendering/core-rendering-server-internal/src/__snapshots__/rendering_service.test.ts.snap index 535624e4a8320..b6fedfd8644e4 100644 --- a/packages/core/rendering/core-rendering-server-internal/src/__snapshots__/rendering_service.test.ts.snap +++ b/packages/core/rendering/core-rendering-server-internal/src/__snapshots__/rendering_service.test.ts.snap @@ -24,6 +24,7 @@ Object { "buildFlavor": Any, "buildNum": Any, "buildSha": Any, + "buildShaShort": "XXXXXX", "dist": Any, "version": Any, }, @@ -92,6 +93,7 @@ Object { "buildFlavor": Any, "buildNum": Any, "buildSha": Any, + "buildShaShort": "XXXXXX", "dist": Any, "version": Any, }, @@ -156,6 +158,7 @@ Object { "buildFlavor": Any, "buildNum": Any, "buildSha": Any, + "buildShaShort": "XXXXXX", "dist": Any, "version": Any, }, @@ -224,6 +227,7 @@ Object { "buildFlavor": Any, "buildNum": Any, "buildSha": Any, + "buildShaShort": "XXXXXX", "dist": Any, "version": Any, }, @@ -288,6 +292,7 @@ Object { "buildFlavor": Any, "buildNum": Any, "buildSha": Any, + "buildShaShort": "XXXXXX", "dist": Any, "version": Any, }, @@ -352,6 +357,7 @@ Object { "buildFlavor": Any, "buildNum": Any, "buildSha": Any, + "buildShaShort": "XXXXXX", "dist": Any, "version": Any, }, @@ -420,6 +426,7 @@ Object { "buildFlavor": Any, "buildNum": Any, "buildSha": Any, + "buildShaShort": "XXXXXX", "dist": Any, "version": Any, }, @@ -484,6 +491,7 @@ Object { "buildFlavor": Any, "buildNum": Any, "buildSha": Any, + "buildShaShort": "XXXXXX", "dist": Any, "version": Any, }, @@ -553,6 +561,7 @@ Object { "buildFlavor": Any, "buildNum": Any, "buildSha": Any, + "buildShaShort": "XXXXXX", "dist": Any, "version": Any, }, @@ -621,6 +630,7 @@ Object { "buildFlavor": Any, "buildNum": Any, "buildSha": Any, + "buildShaShort": "XXXXXX", "dist": Any, "version": Any, }, @@ -690,6 +700,7 @@ Object { "buildFlavor": Any, "buildNum": Any, "buildSha": Any, + "buildShaShort": "XXXXXX", "dist": Any, "version": Any, }, @@ -763,6 +774,7 @@ Object { "buildFlavor": Any, "buildNum": Any, "buildSha": Any, + "buildShaShort": "XXXXXX", "dist": Any, "version": Any, }, @@ -827,6 +839,7 @@ Object { "buildFlavor": Any, "buildNum": Any, "buildSha": Any, + "buildShaShort": "XXXXXX", "dist": Any, "version": Any, }, @@ -896,6 +909,7 @@ Object { "buildFlavor": Any, "buildNum": Any, "buildSha": Any, + "buildShaShort": "XXXXXX", "dist": Any, "version": Any, }, @@ -969,6 +983,7 @@ Object { "buildFlavor": Any, "buildNum": Any, "buildSha": Any, + "buildShaShort": "XXXXXX", "dist": Any, "version": Any, }, @@ -1038,6 +1053,7 @@ Object { "buildFlavor": Any, "buildNum": Any, "buildSha": Any, + "buildShaShort": "XXXXXX", "dist": Any, "version": Any, }, diff --git a/packages/core/rendering/core-rendering-server-internal/src/bootstrap/bootstrap_renderer.test.ts b/packages/core/rendering/core-rendering-server-internal/src/bootstrap/bootstrap_renderer.test.ts index 5c699e905e9cd..e959b3aff356d 100644 --- a/packages/core/rendering/core-rendering-server-internal/src/bootstrap/bootstrap_renderer.test.ts +++ b/packages/core/rendering/core-rendering-server-internal/src/bootstrap/bootstrap_renderer.test.ts @@ -25,6 +25,7 @@ const createPackageInfo = (parts: Partial = {}): PackageInfo => ({ branch: 'master', buildNum: 42, buildSha: 'buildSha', + buildShaShort: 'buildShaShort', buildDate: new Date('2023-05-15T23:12:09.000Z'), dist: false, version: '8.0.0', @@ -62,7 +63,7 @@ describe('bootstrapRenderer', () => { auth, packageInfo, uiPlugins, - baseHref: '/base-path', + baseHref: `/base-path/${packageInfo.buildShaShort}`, // the base href as provided by static assets module }); }); @@ -319,7 +320,7 @@ describe('bootstrapRenderer', () => { expect(getPluginsBundlePathsMock).toHaveBeenCalledWith({ isAnonymousPage, uiPlugins, - bundlesHref: '/base-path/42/bundles', + bundlesHref: '/base-path/buildShaShort/bundles', }); }); }); @@ -338,7 +339,7 @@ describe('bootstrapRenderer', () => { expect(getJsDependencyPathsMock).toHaveBeenCalledTimes(1); expect(getJsDependencyPathsMock).toHaveBeenCalledWith( - '/base-path/42/bundles', + '/base-path/buildShaShort/bundles', pluginsBundlePaths ); }); diff --git a/packages/core/rendering/core-rendering-server-internal/src/bootstrap/bootstrap_renderer.ts b/packages/core/rendering/core-rendering-server-internal/src/bootstrap/bootstrap_renderer.ts index e8c30819a0b6e..57cd247b4f6f5 100644 --- a/packages/core/rendering/core-rendering-server-internal/src/bootstrap/bootstrap_renderer.ts +++ b/packages/core/rendering/core-rendering-server-internal/src/bootstrap/bootstrap_renderer.ts @@ -79,8 +79,7 @@ export const bootstrapRendererFactory: BootstrapRendererFactory = ({ themeVersion, darkMode, }); - const buildHash = packageInfo.buildNum; - const bundlesHref = getBundlesHref(baseHref, String(buildHash)); + const bundlesHref = getBundlesHref(baseHref); const bundlePaths = getPluginsBundlePaths({ uiPlugins, diff --git a/packages/core/rendering/core-rendering-server-internal/src/render_utils.test.ts b/packages/core/rendering/core-rendering-server-internal/src/render_utils.test.ts index 8a5d2e4c7377b..e52e18e03776b 100644 --- a/packages/core/rendering/core-rendering-server-internal/src/render_utils.test.ts +++ b/packages/core/rendering/core-rendering-server-internal/src/render_utils.test.ts @@ -16,14 +16,14 @@ describe('getStylesheetPaths', () => { getStylesheetPaths({ darkMode: true, themeVersion: 'v8', - baseHref: '/base-path', + baseHref: '/base-path/buildShaShort', buildNum: 17, }) ).toMatchInlineSnapshot(` Array [ - "/base-path/17/bundles/kbn-ui-shared-deps-npm/kbn-ui-shared-deps-npm.v8.dark.css", - "/base-path/17/bundles/kbn-ui-shared-deps-src/kbn-ui-shared-deps-src.css", - "/base-path/ui/legacy_dark_theme.min.css", + "/base-path/buildShaShort/bundles/kbn-ui-shared-deps-npm/kbn-ui-shared-deps-npm.v8.dark.css", + "/base-path/buildShaShort/bundles/kbn-ui-shared-deps-src/kbn-ui-shared-deps-src.css", + "/base-path/buildShaShort/ui/legacy_dark_theme.min.css", ] `); }); @@ -36,14 +36,14 @@ describe('getStylesheetPaths', () => { getStylesheetPaths({ darkMode: false, themeVersion: 'v8', - baseHref: '/base-path', + baseHref: '/base-path/buildShaShort', buildNum: 69, }) ).toMatchInlineSnapshot(` Array [ - "/base-path/69/bundles/kbn-ui-shared-deps-npm/kbn-ui-shared-deps-npm.v8.light.css", - "/base-path/69/bundles/kbn-ui-shared-deps-src/kbn-ui-shared-deps-src.css", - "/base-path/ui/legacy_light_theme.min.css", + "/base-path/buildShaShort/bundles/kbn-ui-shared-deps-npm/kbn-ui-shared-deps-npm.v8.light.css", + "/base-path/buildShaShort/bundles/kbn-ui-shared-deps-src/kbn-ui-shared-deps-src.css", + "/base-path/buildShaShort/ui/legacy_light_theme.min.css", ] `); }); diff --git a/packages/core/rendering/core-rendering-server-internal/src/render_utils.ts b/packages/core/rendering/core-rendering-server-internal/src/render_utils.ts index 6f74320098b16..51f15a2ba034d 100644 --- a/packages/core/rendering/core-rendering-server-internal/src/render_utils.ts +++ b/packages/core/rendering/core-rendering-server-internal/src/render_utils.ts @@ -22,8 +22,7 @@ export const getSettingValue = ( return convert(value); }; -export const getBundlesHref = (baseHref: string, buildNr: string): string => - `${baseHref}/${buildNr}/bundles`; +export const getBundlesHref = (baseHref: string): string => `${baseHref}/bundles`; export const getStylesheetPaths = ({ themeVersion, @@ -36,7 +35,7 @@ export const getStylesheetPaths = ({ buildNum: number; baseHref: string; }) => { - const bundlesHref = getBundlesHref(baseHref, String(buildNum)); + const bundlesHref = getBundlesHref(baseHref); return [ ...(darkMode ? [ diff --git a/packages/kbn-coloring/src/palettes/constants.ts b/packages/kbn-coloring/src/palettes/constants.ts index ee9bb9b02b29b..fb35d51c0db13 100644 --- a/packages/kbn-coloring/src/palettes/constants.ts +++ b/packages/kbn-coloring/src/palettes/constants.ts @@ -14,3 +14,7 @@ export const DEFAULT_RANGE_TYPE = 'percent'; export const DEFAULT_MIN_STOP = 0; export const DEFAULT_MAX_STOP = 100; export const DEFAULT_COLOR_STEPS = 5; + +export const DEFAULT_FALLBACK_PALETTE = 'default'; +export const LEGACY_COMPLIMENTARY_PALETTE = 'complimentary'; +export const COMPLEMENTARY_PALETTE = 'complementary'; diff --git a/packages/kbn-coloring/src/palettes/utils.ts b/packages/kbn-coloring/src/palettes/utils.ts index d3a51388c7a3d..406da5fb7c998 100644 --- a/packages/kbn-coloring/src/palettes/utils.ts +++ b/packages/kbn-coloring/src/palettes/utils.ts @@ -19,6 +19,9 @@ import { DEFAULT_PALETTE_NAME, DEFAULT_MAX_STOP, DEFAULT_MIN_STOP, + DEFAULT_FALLBACK_PALETTE, + LEGACY_COMPLIMENTARY_PALETTE, + COMPLEMENTARY_PALETTE, } from './constants'; /** @internal **/ @@ -189,3 +192,14 @@ export function reversePalette(paletteColorRepresentation: ColorStop[] = []) { })) .reverse(); } + +// This is a helper function used for backwards compatibility +// for the mispelled complementary palette. +// https://github.com/elastic/kibana/issues/161194 +export function getActivePaletteName(name?: string): string { + let paletteName = name || DEFAULT_FALLBACK_PALETTE; + if (paletteName === LEGACY_COMPLIMENTARY_PALETTE) { + paletteName = COMPLEMENTARY_PALETTE; + } + return paletteName; +} diff --git a/packages/kbn-config/src/__snapshots__/env.test.ts.snap b/packages/kbn-config/src/__snapshots__/env.test.ts.snap index e5d5a3816ced3..4bc87fb5f240e 100644 --- a/packages/kbn-config/src/__snapshots__/env.test.ts.snap +++ b/packages/kbn-config/src/__snapshots__/env.test.ts.snap @@ -32,6 +32,7 @@ Env { "buildFlavor": "traditional", "buildNum": 9007199254740991, "buildSha": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", + "buildShaShort": "XXXXXXXXXXXX", "dist": false, "version": "v1", }, @@ -75,6 +76,7 @@ Env { "buildFlavor": "traditional", "buildNum": 9007199254740991, "buildSha": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", + "buildShaShort": "XXXXXXXXXXXX", "dist": false, "version": "v1", }, @@ -117,6 +119,7 @@ Env { "buildFlavor": "traditional", "buildNum": 9007199254740991, "buildSha": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", + "buildShaShort": "XXXXXXXXXXXX", "dist": false, "version": "some-version", }, @@ -159,6 +162,7 @@ Env { "buildFlavor": "traditional", "buildNum": 100, "buildSha": "feature-v1-build-sha", + "buildShaShort": "feature-v1-b", "dist": true, "version": "v1", }, @@ -201,6 +205,7 @@ Env { "buildFlavor": "traditional", "buildNum": 9007199254740991, "buildSha": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", + "buildShaShort": "XXXXXXXXXXXX", "dist": false, "version": "v1", }, @@ -243,6 +248,7 @@ Env { "buildFlavor": "traditional", "buildNum": 100, "buildSha": "feature-v1-build-sha", + "buildShaShort": "feature-v1-b", "dist": true, "version": "v1", }, diff --git a/packages/kbn-config/src/env.test.ts b/packages/kbn-config/src/env.test.ts index 7c301ff83e6f4..45f037500b77e 100644 --- a/packages/kbn-config/src/env.test.ts +++ b/packages/kbn-config/src/env.test.ts @@ -248,3 +248,35 @@ describe('packageInfo.buildFlavor', () => { expect(env.packageInfo.buildFlavor).toEqual('traditional'); }); }); + +describe('packageInfo.buildShaShort', () => { + const sha = 'c6e1a25bea71a623929a8f172c0273bf0c811ca0'; + it('provides the sha and a short version of the sha', () => { + mockPackage.raw = { + branch: 'some-branch', + version: 'some-version', + }; + + const env = new Env( + '/some/home/dir', + { + branch: 'whathaveyou', + version: 'v1', + build: { + distributable: true, + number: 100, + sha, + date: BUILD_DATE, + }, + }, + getEnvOptions({ + cliArgs: { dev: false }, + configs: ['/some/other/path/some-kibana.yml'], + repoPackages: ['FakePackage1', 'FakePackage2'] as unknown as Package[], + }) + ); + + expect(env.packageInfo.buildSha).toEqual('c6e1a25bea71a623929a8f172c0273bf0c811ca0'); + expect(env.packageInfo.buildShaShort).toEqual('c6e1a25bea71'); + }); +}); diff --git a/packages/kbn-config/src/env.ts b/packages/kbn-config/src/env.ts index 99728f0dfc413..4b2c936116159 100644 --- a/packages/kbn-config/src/env.ts +++ b/packages/kbn-config/src/env.ts @@ -121,6 +121,7 @@ export class Env { branch: pkg.branch, buildNum: isKibanaDistributable ? pkg.build.number : Number.MAX_SAFE_INTEGER, buildSha: isKibanaDistributable ? pkg.build.sha : 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', + buildShaShort: isKibanaDistributable ? pkg.build.sha.slice(0, 12) : 'XXXXXXXXXXXX', version: pkg.version, dist: isKibanaDistributable, buildDate: isKibanaDistributable ? new Date(pkg.build.date) : new Date(), diff --git a/packages/kbn-config/src/types.ts b/packages/kbn-config/src/types.ts index f9038a1a7fd26..91706bb9f2cb8 100644 --- a/packages/kbn-config/src/types.ts +++ b/packages/kbn-config/src/types.ts @@ -14,6 +14,7 @@ export interface PackageInfo { branch: string; buildNum: number; buildSha: string; + buildShaShort: string; buildDate: Date; buildFlavor: BuildFlavor; dist: boolean; diff --git a/packages/kbn-data-view-utils/README.md b/packages/kbn-data-view-utils/README.md new file mode 100644 index 0000000000000..5912eae9f2b82 --- /dev/null +++ b/packages/kbn-data-view-utils/README.md @@ -0,0 +1,3 @@ +# @kbn/data-view-utils + +Data View utilities. \ No newline at end of file diff --git a/packages/kbn-data-view-utils/index.ts b/packages/kbn-data-view-utils/index.ts new file mode 100644 index 0000000000000..c78869a471cb0 --- /dev/null +++ b/packages/kbn-data-view-utils/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './src/constants'; diff --git a/packages/kbn-data-view-utils/jest.config.js b/packages/kbn-data-view-utils/jest.config.js new file mode 100644 index 0000000000000..3c0a0a118baaf --- /dev/null +++ b/packages/kbn-data-view-utils/jest.config.js @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/packages/kbn-data-view-utils'], +}; diff --git a/packages/kbn-data-view-utils/kibana.jsonc b/packages/kbn-data-view-utils/kibana.jsonc new file mode 100644 index 0000000000000..a5bd7b958e272 --- /dev/null +++ b/packages/kbn-data-view-utils/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-common", + "id": "@kbn/data-view-utils", + "owner": "@elastic/kibana-data-discovery" +} diff --git a/packages/kbn-data-view-utils/package.json b/packages/kbn-data-view-utils/package.json new file mode 100644 index 0000000000000..1bcf593b4c438 --- /dev/null +++ b/packages/kbn-data-view-utils/package.json @@ -0,0 +1,7 @@ +{ + "name": "@kbn/data-view-utils", + "private": true, + "version": "1.0.0", + "license": "SSPL-1.0 OR Elastic License 2.0", + "sideEffects": false +} \ No newline at end of file diff --git a/packages/kbn-data-view-utils/src/constants.ts b/packages/kbn-data-view-utils/src/constants.ts new file mode 100644 index 0000000000000..81759978ef93c --- /dev/null +++ b/packages/kbn-data-view-utils/src/constants.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. + */ + +/** + * ESQL type constant + */ + +export const ESQL_TYPE = 'esql'; diff --git a/packages/kbn-data-view-utils/tsconfig.json b/packages/kbn-data-view-utils/tsconfig.json new file mode 100644 index 0000000000000..a41af0b7c5017 --- /dev/null +++ b/packages/kbn-data-view-utils/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node", + "react" + ] + }, + "include": [ + "**/*.ts", + "**/*.tsx", + ], + "exclude": [ + "target/**/*" + ] +} diff --git a/packages/kbn-doc-links/src/get_doc_links.ts b/packages/kbn-doc-links/src/get_doc_links.ts index 719d8a47043ba..e9f0a27956c3e 100644 --- a/packages/kbn-doc-links/src/get_doc_links.ts +++ b/packages/kbn-doc-links/src/get_doc_links.ts @@ -45,7 +45,9 @@ export const getDocLinks = ({ kibanaBranch, buildFlavor }: GetDocLinkOptions): D return deepFreeze({ settings: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/settings.html`, - elasticStackGetStarted: `${ELASTIC_WEBSITE_URL}guide/en/index.html`, + elasticStackGetStarted: isServerless + ? `${SERVERLESS_DOCS}` + : `${ELASTIC_WEBSITE_URL}guide/en/index.html`, upgrade: { upgradingStackOnPrem: `${ELASTIC_WEBSITE_URL}guide/en/elastic-stack/current/upgrading-elastic-stack-on-prem.html`, upgradingStackOnCloud: `${ELASTIC_WEBSITE_URL}guide/en/elastic-stack/current/upgrade-elastic-stack-for-elastic-cloud.html`, diff --git a/packages/kbn-es-types/src/search.ts b/packages/kbn-es-types/src/search.ts index 461a32f149842..4351ac91e5c17 100644 --- a/packages/kbn-es-types/src/search.ts +++ b/packages/kbn-es-types/src/search.ts @@ -678,4 +678,5 @@ export interface ESQLSearchParams { query: string; filter?: unknown; locale?: string; + dropNullColumns?: boolean; } diff --git a/packages/kbn-esql-utils/src/utils/get_esql_adhoc_dataview.ts b/packages/kbn-esql-utils/src/utils/get_esql_adhoc_dataview.ts index a1866526742cc..e1e599946ef59 100644 --- a/packages/kbn-esql-utils/src/utils/get_esql_adhoc_dataview.ts +++ b/packages/kbn-esql-utils/src/utils/get_esql_adhoc_dataview.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; +import { ESQL_TYPE } from '@kbn/data-view-utils'; // uses browser sha256 method with fallback if unavailable async function sha256(str: string) { @@ -32,6 +33,7 @@ export async function getESQLAdHocDataview( ) { return await dataViewsService.create({ title: indexPattern, + type: ESQL_TYPE, id: await sha256(`esql-${indexPattern}`), }); } diff --git a/packages/kbn-esql-utils/src/utils/query_parsing_helpers.test.ts b/packages/kbn-esql-utils/src/utils/query_parsing_helpers.test.ts index 8390e059b4db8..7820d6d9cf7d1 100644 --- a/packages/kbn-esql-utils/src/utils/query_parsing_helpers.test.ts +++ b/packages/kbn-esql-utils/src/utils/query_parsing_helpers.test.ts @@ -43,6 +43,11 @@ describe('sql/esql query helpers', () => { 'SELECT * FROM (SELECT woof, miaou FROM "logstash-1234!" GROUP BY woof)' ); expect(idxPattern8).toBe('logstash-1234!'); + + const idxPattern9 = getIndexPatternFromSQLQuery( + 'SELECT * FROM remote_cluster:logs-* WHERE field > 20' + ); + expect(idxPattern9).toBe('remote_cluster:logs-*'); }); }); @@ -71,6 +76,9 @@ describe('sql/esql query helpers', () => { const idxPattern9 = getIndexPatternFromESQLQuery('FROM foo-1, foo-2 [metadata _id]'); expect(idxPattern9).toBe('foo-1, foo-2'); + + const idxPattern10 = getIndexPatternFromESQLQuery('FROM foo-1, remote_cluster:foo-2, foo-3'); + expect(idxPattern10).toBe('foo-1, remote_cluster:foo-2, foo-3'); }); }); diff --git a/packages/kbn-esql-utils/src/utils/query_parsing_helpers.ts b/packages/kbn-esql-utils/src/utils/query_parsing_helpers.ts index 93e7ddbb6ffe9..6f96a0ae445e8 100644 --- a/packages/kbn-esql-utils/src/utils/query_parsing_helpers.ts +++ b/packages/kbn-esql-utils/src/utils/query_parsing_helpers.ts @@ -17,7 +17,7 @@ export function getIndexPatternFromSQLQuery(sqlQuery?: string): string { sql = `${splitFroms[fromsLength - 2]} FROM ${splitFroms[fromsLength - 1]}`; } // case insensitive match for the index pattern - const regex = new RegExp(/FROM\s+([\w*-.!@$^()~;]+)/, 'i'); + const regex = new RegExp(/FROM\s+([(\w*:)?\w*-.!@$^()~;]+)/, 'i'); const matches = sql?.match(regex); if (matches) { return matches[1]; @@ -34,7 +34,7 @@ export function getIndexPatternFromESQLQuery(esql?: string): string { } const parsedString = esql?.replaceAll('`', ''); // case insensitive match for the index pattern - const regex = new RegExp(/FROM\s+([\w*-.!@$^()~;\s]+)/, 'i'); + const regex = new RegExp(/FROM\s+([(\w*:)?\w*-.!@$^()~;\s]+)/, 'i'); const matches = parsedString?.match(regex); if (matches) { return matches[1]?.trim(); diff --git a/packages/kbn-esql-utils/tsconfig.json b/packages/kbn-esql-utils/tsconfig.json index 2fe775fb7d586..b604fa84c1de3 100644 --- a/packages/kbn-esql-utils/tsconfig.json +++ b/packages/kbn-esql-utils/tsconfig.json @@ -18,5 +18,6 @@ "kbn_references": [ "@kbn/data-views-plugin", "@kbn/crypto-browser", + "@kbn/data-view-utils", ] } diff --git a/packages/kbn-generate-csv/src/__snapshots__/generate_csv.test.ts.snap b/packages/kbn-generate-csv/src/__snapshots__/generate_csv.test.ts.snap index 778ec83563ca7..ec692fe0fea60 100644 --- a/packages/kbn-generate-csv/src/__snapshots__/generate_csv.test.ts.snap +++ b/packages/kbn-generate-csv/src/__snapshots__/generate_csv.test.ts.snap @@ -226,6 +226,14 @@ exports[`CsvGenerator Scroll strategy uses the scroll context to page all the da " `; +exports[`CsvGenerator export behavior when scroll duration config is auto csv gets generated if search resolves without errors before the computed timeout value passed to the search data client elapses 1`] = ` +"a,b +a1,b1 +a1,b1 +a1,b1 +" +`; + exports[`CsvGenerator fields from job.columns (7.13+ generated) cells can be multi-value 1`] = ` "product,category coconut,\\"cool, rad\\" diff --git a/packages/kbn-generate-csv/src/generate_csv.test.ts b/packages/kbn-generate-csv/src/generate_csv.test.ts index 8d831895173ed..86a88fe3d3fb0 100644 --- a/packages/kbn-generate-csv/src/generate_csv.test.ts +++ b/packages/kbn-generate-csv/src/generate_csv.test.ts @@ -9,6 +9,7 @@ import { identity, range } from 'lodash'; import * as Rx from 'rxjs'; import type { Writable } from 'stream'; +import { add, type Duration } from 'date-fns'; import { errors as esErrors, estypes } from '@elastic/elasticsearch'; import type { SearchResponse } from '@elastic/elasticsearch/lib/api/types'; @@ -21,6 +22,8 @@ import { } from '@kbn/core/server/mocks'; import { ISearchClient, ISearchStartSearchSource } from '@kbn/data-plugin/common'; import { searchSourceInstanceMock } from '@kbn/data-plugin/common/search/search_source/mocks'; +import type { IScopedSearchClient } from '@kbn/data-plugin/server'; +import type { IKibanaSearchResponse } from '@kbn/data-plugin/common'; import { dataPluginMock } from '@kbn/data-plugin/server/mocks'; import { FieldFormatsRegistry } from '@kbn/field-formats-plugin/common'; import { CancellationToken } from '@kbn/reporting-common'; @@ -360,7 +363,11 @@ describe('CsvGenerator', () => { expect(mockDataClient.search).toHaveBeenCalledTimes(10); expect(mockDataClient.search).toBeCalledWith( { params: { body: {}, ignore_throttled: undefined, max_concurrent_shard_requests: 5 } }, - { strategy: 'es', transport: { maxRetries: 0, requestTimeout: '30s' } } + { + abortSignal: expect.any(AbortSignal), + strategy: 'es', + transport: { maxRetries: 0, requestTimeout: '30s' }, + } ); expect(mockEsClient.asCurrentUser.openPointInTime).toHaveBeenCalledTimes(1); @@ -370,7 +377,12 @@ describe('CsvGenerator', () => { index: 'logstash-*', keep_alive: '30s', }, - { maxConcurrentShardRequests: 5, maxRetries: 0, requestTimeout: '30s' } + { + maxConcurrentShardRequests: 5, + maxRetries: 0, + requestTimeout: '30s', + signal: expect.any(AbortSignal), + } ); expect(mockEsClient.asCurrentUser.closePointInTime).toHaveBeenCalledTimes(1); @@ -548,6 +560,203 @@ describe('CsvGenerator', () => { }); }); + describe('export behavior when scroll duration config is auto', () => { + const getTaskInstanceFields = (intervalFromNow: Duration) => { + const now = new Date(Date.now()); + return { startedAt: now, retryAt: add(now, intervalFromNow) }; + }; + + let mockConfigWithAutoScrollDuration: ReportingConfigType['csv']; + let mockDataClientSearchFn: jest.MockedFunction; + + beforeEach(() => { + mockConfigWithAutoScrollDuration = { + ...mockConfig, + scroll: { + ...mockConfig.scroll, + duration: 'auto', + }, + }; + + mockDataClientSearchFn = jest.fn(); + + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.clearAllTimers(); + jest.useRealTimers(); + + mockDataClientSearchFn.mockRestore(); + }); + + it('csv gets generated if search resolves without errors before the computed timeout value passed to the search data client elapses', async () => { + const timeFromNowInMs = 4 * 60 * 1000; + + const taskInstanceFields = getTaskInstanceFields({ + seconds: timeFromNowInMs / 1000, + }); + + mockDataClientSearchFn.mockImplementation((_, options) => { + const getSearchResult = () => { + const queuedAt = Date.now(); + + return new Promise>>( + (resolve, reject) => { + setTimeout(() => { + if ( + new Date(Date.now()).getTime() - new Date(queuedAt).getTime() > + Number((options?.transport?.requestTimeout! as string).replace(/ms/, '')) + ) { + reject( + new esErrors.ResponseError({ statusCode: 408, meta: {} as any, warnings: [] }) + ); + } else { + resolve({ + rawResponse: getMockRawResponse( + [ + { + fields: { a: ['a1'], b: ['b1'] }, + } as unknown as estypes.SearchHit, + ], + 3 + ), + }); + } + }, timeFromNowInMs / 4); + } + ); + }; + + return Rx.defer(getSearchResult); + }); + + const generateCsvPromise = new CsvGenerator( + createMockJob({ searchSource: {}, columns: ['a', 'b'] }), + mockConfigWithAutoScrollDuration, + taskInstanceFields, + { + es: mockEsClient, + data: { + ...mockDataClient, + search: mockDataClientSearchFn, + }, + uiSettings: uiSettingsClient, + }, + { + searchSourceStart: mockSearchSourceService, + fieldFormatsRegistry: mockFieldFormatsRegistry, + }, + new CancellationToken(), + mockLogger, + stream + ).generateData(); + + await jest.advanceTimersByTimeAsync(timeFromNowInMs); + + expect(await generateCsvPromise).toEqual( + expect.objectContaining({ + warnings: [], + }) + ); + + expect(mockDataClientSearchFn).toBeCalledWith( + { params: { body: {}, ignore_throttled: undefined, max_concurrent_shard_requests: 5 } }, + { + abortSignal: expect.any(AbortSignal), + strategy: 'es', + transport: { maxRetries: 0, requestTimeout: `${timeFromNowInMs}ms` }, + } + ); + + expect(content).toMatchSnapshot(); + }); + + it('csv generation errors if search request does not resolve before the computed timeout value passed to the search data client elapses', async () => { + const timeFromNowInMs = 4 * 60 * 1000; + + const taskInstanceFields = getTaskInstanceFields({ + seconds: timeFromNowInMs / 1000, + }); + + const requestDuration = timeFromNowInMs + 1000; + + mockDataClientSearchFn.mockImplementation((_, options) => { + const getSearchResult = () => { + const queuedAt = Date.now(); + + return new Promise>>( + (resolve, reject) => { + setTimeout(() => { + if ( + new Date(Date.now()).getTime() - new Date(queuedAt).getTime() > + Number((options?.transport?.requestTimeout! as string).replace(/ms/, '')) + ) { + reject( + new esErrors.ResponseError({ statusCode: 408, meta: {} as any, warnings: [] }) + ); + } else { + resolve({ + rawResponse: getMockRawResponse( + [ + { + fields: { a: ['a1'], b: ['b1'] }, + } as unknown as estypes.SearchHit, + ], + 3 + ), + }); + } + }, requestDuration); + } + ); + }; + + return Rx.defer(getSearchResult); + }); + + const generateCsvPromise = new CsvGenerator( + createMockJob({ searchSource: {}, columns: ['a', 'b'] }), + mockConfigWithAutoScrollDuration, + taskInstanceFields, + { + es: mockEsClient, + data: { + ...mockDataClient, + search: mockDataClientSearchFn, + }, + uiSettings: uiSettingsClient, + }, + { + searchSourceStart: mockSearchSourceService, + fieldFormatsRegistry: mockFieldFormatsRegistry, + }, + new CancellationToken(), + mockLogger, + stream + ).generateData(); + + await jest.advanceTimersByTimeAsync(requestDuration); + + expect(await generateCsvPromise).toEqual( + expect.objectContaining({ + warnings: expect.arrayContaining([ + expect.stringContaining('Received a 408 response from Elasticsearch'), + ]), + }) + ); + + expect(mockDataClientSearchFn).toBeCalledWith( + { params: { body: {}, ignore_throttled: undefined, max_concurrent_shard_requests: 5 } }, + { + abortSignal: expect.any(AbortSignal), + strategy: 'es', + transport: { maxRetries: 0, requestTimeout: `${timeFromNowInMs}ms` }, + } + ); + }); + }); + describe('Scroll strategy', () => { const mockJobUsingScrollPaging = createMockJob({ columns: ['date', 'ip', 'message'], @@ -654,7 +863,11 @@ describe('CsvGenerator', () => { max_concurrent_shard_requests: 5, }), }, - { strategy: 'es', transport: { maxRetries: 0, requestTimeout: '30s' } } + { + abortSignal: expect.any(AbortSignal), + strategy: 'es', + transport: { maxRetries: 0, requestTimeout: '30s' }, + } ); expect(mockEsClient.asCurrentUser.openPointInTime).not.toHaveBeenCalled(); @@ -1200,17 +1413,12 @@ describe('CsvGenerator', () => { index: 'logstash-*', keep_alive: '30s', }, - { maxConcurrentShardRequests: 5, maxRetries: 0, requestTimeout: '30s' } - ); - - expect(mockEsClient.asCurrentUser.openPointInTime).toHaveBeenCalledWith( { - ignore_unavailable: true, - ignore_throttled: false, - index: 'logstash-*', - keep_alive: '30s', - }, - { maxConcurrentShardRequests: 5, maxRetries: 0, requestTimeout: '30s' } + maxConcurrentShardRequests: 5, + maxRetries: 0, + requestTimeout: '30s', + signal: expect.any(AbortSignal), + } ); expect(mockDataClient.search).toBeCalledWith( @@ -1220,7 +1428,11 @@ describe('CsvGenerator', () => { max_concurrent_shard_requests: 5, }, }, - { strategy: 'es', transport: { maxRetries: 0, requestTimeout: '30s' } } + { + abortSignal: expect.any(AbortSignal), + strategy: 'es', + transport: { maxRetries: 0, requestTimeout: '30s' }, + } ); }); diff --git a/packages/kbn-generate-csv/src/generate_csv.ts b/packages/kbn-generate-csv/src/generate_csv.ts index 18ee2cdb0005a..c59e4fd40aafb 100644 --- a/packages/kbn-generate-csv/src/generate_csv.ts +++ b/packages/kbn-generate-csv/src/generate_csv.ts @@ -21,9 +21,9 @@ import type { } from '@kbn/field-formats-plugin/common'; import { AuthenticationExpiredError, + byteSizeValueToNumber, CancellationToken, ReportingError, - byteSizeValueToNumber, } from '@kbn/reporting-common'; import type { TaskInstanceFields, TaskRunResult } from '@kbn/reporting-common/types'; import type { ReportingConfigType } from '@kbn/reporting-server'; @@ -63,7 +63,6 @@ export class CsvGenerator { private logger: Logger, private stream: Writable ) {} - /* * Load field formats for each field in the list */ @@ -180,9 +179,9 @@ export class CsvGenerator { /* * Intrinsically, generating the rows is a synchronous process. Awaiting - * on a setImmediate call here partititions what could be a very long and - * CPU-intenstive synchronous process into asychronous processes. This - * give NodeJS to process other asychronous events that wait on the Event + * on a setImmediate call here partitions what could be a very long and + * CPU-intensive synchronous process into asynchronous processes. This + * give NodeJS to process other asynchronous events that wait on the Event * Loop. * * See: https://nodejs.org/en/docs/guides/dont-block-the-event-loop/ @@ -225,7 +224,13 @@ export class CsvGenerator { public async generateData(): Promise { const logger = this.logger; const [settings, searchSource] = await Promise.all([ - getExportSettings(this.clients.uiSettings, this.config, this.job.browserTimezone, logger), + getExportSettings( + this.clients.uiSettings, + this.taskInstanceFields, + this.config, + this.job.browserTimezone, + logger + ), this.dependencies.searchSourceStart.create(this.job.searchSource), ]); @@ -252,15 +257,30 @@ export class CsvGenerator { let totalRecords: number | undefined; let reportingError: undefined | ReportingError; + const abortController = new AbortController(); + this.cancellationToken.on(() => abortController.abort()); + // use a class to internalize the paging strategy let cursor: SearchCursor; if (this.job.pagingStrategy === 'scroll') { // Optional strategy: scan-and-scroll - cursor = new SearchCursorScroll(indexPatternTitle, settings, this.clients, this.logger); + cursor = new SearchCursorScroll( + indexPatternTitle, + settings, + this.clients, + abortController, + this.logger + ); logger.debug('Using search strategy: scroll'); } else { // Default strategy: point-in-time - cursor = new SearchCursorPit(indexPatternTitle, settings, this.clients, this.logger); + cursor = new SearchCursorPit( + indexPatternTitle, + settings, + this.clients, + abortController, + this.logger + ); logger.debug('Using search strategy: pit'); } await cursor.initialize(); @@ -289,6 +309,7 @@ export class CsvGenerator { if (this.cancellationToken.isCancelled()) { break; } + searchSource.setField('size', settings.scroll.size); let results: estypes.SearchResponse | undefined; @@ -406,7 +427,7 @@ export class CsvGenerator { /* * Add the errors into the CSV content. This makes error messages more * discoverable. When the export was automated or triggered by an API - * call or is automated, the user doesn't necesssarily go through the + * call or is automated, the user doesn't necessarily go through the * Kibana UI to download the export and might not otherwise see the * error message. */ diff --git a/packages/kbn-generate-csv/src/generate_csv_esql.test.ts b/packages/kbn-generate-csv/src/generate_csv_esql.test.ts index 3ef6e3b2318a9..3e925893d37e4 100644 --- a/packages/kbn-generate-csv/src/generate_csv_esql.test.ts +++ b/packages/kbn-generate-csv/src/generate_csv_esql.test.ts @@ -8,6 +8,7 @@ import * as Rx from 'rxjs'; import type { Writable } from 'stream'; +import { add, type Duration } from 'date-fns'; import { errors as esErrors } from '@elastic/elasticsearch'; import type { IScopedClusterClient, IUiSettingsClient, Logger } from '@kbn/core/server'; @@ -20,9 +21,9 @@ import { import { IKibanaSearchResponse } from '@kbn/data-plugin/common'; import { IScopedSearchClient } from '@kbn/data-plugin/server'; import { dataPluginMock } from '@kbn/data-plugin/server/mocks'; -import type { ESQLSearchReponse } from '@kbn/es-types'; import { CancellationToken } from '@kbn/reporting-common'; import type { ReportingConfigType } from '@kbn/reporting-server'; +import type { ESQLSearchReponse as ESQLSearchResponse } from '@kbn/es-types'; import { UI_SETTINGS_CSV_QUOTE_VALUES, UI_SETTINGS_CSV_SEPARATOR, @@ -37,6 +38,8 @@ const createMockJob = ( query: { esql: '' }, }); +const mockTaskInstanceFields = { startedAt: null, retryAt: null }; + describe('CsvESQLGenerator', () => { let mockEsClient: IScopedClusterClient; let mockDataClient: IScopedSearchClient; @@ -47,20 +50,20 @@ describe('CsvESQLGenerator', () => { let content: string; const getMockRawResponse = ( - esqlResponse: ESQLSearchReponse = { + esqlResponse: ESQLSearchResponse = { columns: [], values: [], } - ): ESQLSearchReponse => esqlResponse; + ): ESQLSearchResponse => esqlResponse; const mockDataClientSearchDefault = jest.fn().mockImplementation( - (): Rx.Observable> => + (): Rx.Observable> => Rx.of({ rawResponse: getMockRawResponse(), }) ); - const mockSearchResponse = (response: ESQLSearchReponse) => { + const mockSearchResponse = (response: ESQLSearchResponse) => { mockDataClient.search = jest.fn().mockImplementation(() => Rx.of({ rawResponse: getMockRawResponse(response), @@ -105,6 +108,7 @@ describe('CsvESQLGenerator', () => { const generateCsv = new CsvESQLGenerator( createMockJob({ columns: ['date', 'ip', 'message'] }), mockConfig, + mockTaskInstanceFields, { es: mockEsClient, data: mockDataClient, @@ -136,6 +140,7 @@ describe('CsvESQLGenerator', () => { const generateCsv = new CsvESQLGenerator( createMockJob(), mockConfig, + mockTaskInstanceFields, { es: mockEsClient, data: mockDataClient, @@ -163,6 +168,7 @@ describe('CsvESQLGenerator', () => { const generateCsv = new CsvESQLGenerator( createMockJob(), mockConfig, + mockTaskInstanceFields, { es: mockEsClient, data: mockDataClient, @@ -192,6 +198,7 @@ describe('CsvESQLGenerator', () => { const generateCsv = new CsvESQLGenerator( createMockJob(), mockConfig, + mockTaskInstanceFields, { es: mockEsClient, data: mockDataClient, @@ -211,6 +218,189 @@ describe('CsvESQLGenerator', () => { `); }); + describe('"auto" scroll duration config', () => { + const getTaskInstanceFields = (intervalFromNow: Duration) => { + const now = new Date(Date.now()); + return { startedAt: now, retryAt: add(now, intervalFromNow) }; + }; + + let mockConfigWithAutoScrollDuration: ReportingConfigType['csv']; + let mockDataClientSearchFn: jest.MockedFunction; + + beforeEach(() => { + mockConfigWithAutoScrollDuration = { + ...mockConfig, + scroll: { + ...mockConfig.scroll, + duration: 'auto', + }, + }; + + mockDataClientSearchFn = jest.fn(); + + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.clearAllTimers(); + jest.useRealTimers(); + + mockDataClientSearchFn.mockRestore(); + }); + + it('csv gets generated if search resolves without errors before the computed timeout value passed to the search data client elapses', async () => { + const timeFromNowInMs = 4 * 60 * 1000; + + const taskInstanceFields = getTaskInstanceFields({ + seconds: timeFromNowInMs / 1000, + }); + + mockDataClientSearchFn.mockImplementation((_, options) => { + const getSearchResult = () => { + const queuedAt = Date.now(); + + return new Promise>>( + (resolve, reject) => { + setTimeout(() => { + if ( + new Date(Date.now()).getTime() - new Date(queuedAt).getTime() > + Number((options?.transport?.requestTimeout! as string).replace(/ms/, '')) + ) { + reject( + new esErrors.ResponseError({ statusCode: 408, meta: {} as any, warnings: [] }) + ); + } else { + resolve({ + rawResponse: getMockRawResponse({ + columns: [{ name: 'message', type: 'string' }], + values: Array(100).fill(['This is a great message!']), + }), + }); + } + }, timeFromNowInMs / 4); + } + ); + }; + + return Rx.defer(getSearchResult); + }); + + const generateCsvPromise = new CsvESQLGenerator( + createMockJob(), + mockConfigWithAutoScrollDuration, + taskInstanceFields, + { + es: mockEsClient, + data: { + ...mockDataClient, + search: mockDataClientSearchFn, + }, + uiSettings: uiSettingsClient, + }, + new CancellationToken(), + mockLogger, + stream + ).generateData(); + + await jest.advanceTimersByTimeAsync(timeFromNowInMs); + + expect(await generateCsvPromise).toEqual( + expect.objectContaining({ + warnings: [], + }) + ); + + expect(mockDataClientSearchFn).toBeCalledWith( + { params: { filter: undefined, locale: 'en', query: '' } }, + { + strategy: 'esql', + transport: { + requestTimeout: `${timeFromNowInMs}ms`, + }, + abortSignal: expect.any(AbortSignal), + } + ); + }); + + it('csv generation errors if search request does not resolve before the computed timeout value passed to the search data client elapses', async () => { + const timeFromNowInMs = 4 * 60 * 1000; + + const taskInstanceFields = getTaskInstanceFields({ + seconds: timeFromNowInMs / 1000, + }); + + const requestDuration = timeFromNowInMs + 1000; + + mockDataClientSearchFn.mockImplementation((_, options) => { + const getSearchResult = () => { + const queuedAt = Date.now(); + + return new Promise>>( + (resolve, reject) => { + setTimeout(() => { + if ( + new Date(Date.now()).getTime() - new Date(queuedAt).getTime() > + Number((options?.transport?.requestTimeout! as string).replace(/ms/, '')) + ) { + reject( + new esErrors.ResponseError({ statusCode: 408, meta: {} as any, warnings: [] }) + ); + } else { + resolve({ + rawResponse: getMockRawResponse({ + columns: [{ name: 'message', type: 'string' }], + values: Array(100).fill(['This is a great message!']), + }), + }); + } + }, requestDuration); + } + ); + }; + + return Rx.defer(getSearchResult); + }); + + const generateCsvPromise = new CsvESQLGenerator( + createMockJob(), + mockConfigWithAutoScrollDuration, + taskInstanceFields, + { + es: mockEsClient, + data: { + ...mockDataClient, + search: mockDataClientSearchFn, + }, + uiSettings: uiSettingsClient, + }, + new CancellationToken(), + mockLogger, + stream + ).generateData(); + + await jest.advanceTimersByTimeAsync(requestDuration); + + expect(await generateCsvPromise).toEqual( + expect.objectContaining({ + warnings: expect.arrayContaining([ + expect.stringContaining('Received a 408 response from Elasticsearch'), + ]), + }) + ); + + expect(mockDataClientSearchFn).toBeCalledWith( + { params: { filter: undefined, locale: 'en', query: '' } }, + { + strategy: 'esql', + transport: { + requestTimeout: `${timeFromNowInMs}ms`, + }, + abortSignal: expect.any(AbortSignal), + } + ); + }); + }); + describe('jobParams', () => { it('uses columns to select columns', async () => { mockSearchResponse({ @@ -225,6 +415,7 @@ describe('CsvESQLGenerator', () => { const generateCsv = new CsvESQLGenerator( createMockJob({ columns: ['message', 'date', 'something else'] }), mockConfig, + mockTaskInstanceFields, { es: mockEsClient, data: mockDataClient, @@ -259,6 +450,7 @@ describe('CsvESQLGenerator', () => { const generateCsv = new CsvESQLGenerator( createMockJob({ query, filters }), mockConfig, + mockTaskInstanceFields, { es: mockEsClient, data: mockDataClient, @@ -318,6 +510,7 @@ describe('CsvESQLGenerator', () => { const generateCsv = new CsvESQLGenerator( createMockJob(), mockConfig, + mockTaskInstanceFields, { es: mockEsClient, data: mockDataClient, @@ -347,6 +540,7 @@ describe('CsvESQLGenerator', () => { const generateCsv = new CsvESQLGenerator( createMockJob(), mockConfig, + mockTaskInstanceFields, { es: mockEsClient, data: mockDataClient, @@ -385,6 +579,7 @@ describe('CsvESQLGenerator', () => { const generateCsv = new CsvESQLGenerator( createMockJob(), mockConfig, + mockTaskInstanceFields, { es: mockEsClient, data: mockDataClient, @@ -413,6 +608,7 @@ describe('CsvESQLGenerator', () => { const generateCsv = new CsvESQLGenerator( createMockJob(), mockConfig, + mockTaskInstanceFields, { es: mockEsClient, data: mockDataClient, @@ -449,6 +645,7 @@ describe('CsvESQLGenerator', () => { const generateCsv = new CsvESQLGenerator( createMockJob(), mockConfig, + mockTaskInstanceFields, { es: mockEsClient, data: mockDataClient, diff --git a/packages/kbn-generate-csv/src/generate_csv_esql.ts b/packages/kbn-generate-csv/src/generate_csv_esql.ts index 567d4121af219..cea0838460ab5 100644 --- a/packages/kbn-generate-csv/src/generate_csv_esql.ts +++ b/packages/kbn-generate-csv/src/generate_csv_esql.ts @@ -30,6 +30,7 @@ import { } from '@kbn/reporting-common'; import type { TaskRunResult } from '@kbn/reporting-common/types'; import type { ReportingConfigType } from '@kbn/reporting-server'; +import { type TaskInstanceFields } from '@kbn/reporting-common/types'; import { zipObject } from 'lodash'; import { CONTENT_TYPE_CSV } from '../constants'; @@ -58,6 +59,7 @@ export class CsvESQLGenerator { constructor( private job: JobParamsCsvESQL, private config: ReportingConfigType['csv'], + private taskInstanceFields: TaskInstanceFields, private clients: Clients, private cancellationToken: CancellationToken, private logger: Logger, @@ -67,6 +69,7 @@ export class CsvESQLGenerator { public async generateData(): Promise { const settings = await getExportSettings( this.clients.uiSettings, + this.taskInstanceFields, this.config, this.job.browserTimezone, this.logger @@ -111,7 +114,7 @@ export class CsvESQLGenerator { strategy: ESQL_SEARCH_STRATEGY, abortSignal: abortController.signal, transport: { - requestTimeout: settings.scroll.duration, + requestTimeout: settings.scroll.duration(this.taskInstanceFields), }, }) ); diff --git a/packages/kbn-generate-csv/src/lib/get_export_settings.test.ts b/packages/kbn-generate-csv/src/lib/get_export_settings.test.ts index a732bc73f5706..9a9b2e5ff3586 100644 --- a/packages/kbn-generate-csv/src/lib/get_export_settings.test.ts +++ b/packages/kbn-generate-csv/src/lib/get_export_settings.test.ts @@ -13,6 +13,8 @@ import { uiSettingsServiceMock, } from '@kbn/core/server/mocks'; import type { ReportingConfigType } from '@kbn/reporting-server'; +import type { TaskInstanceFields } from '@kbn/reporting-common/types'; +import { sub, add, type Duration } from 'date-fns'; import { UI_SETTINGS_CSV_QUOTE_VALUES, @@ -25,6 +27,7 @@ import { getExportSettings } from './get_export_settings'; describe('getExportSettings', () => { let uiSettingsClient: IUiSettingsClient; let config: ReportingConfigType['csv']; + let taskInstanceFields: TaskInstanceFields; const logger = loggingSystemMock.createLogger(); beforeEach(() => { @@ -38,6 +41,8 @@ describe('getExportSettings', () => { enablePanelActionDownload: true, }; + taskInstanceFields = { startedAt: null, retryAt: null }; + uiSettingsClient = uiSettingsServiceMock .createStartContract() .asScopedToClient(savedObjectsClientMock.create()); @@ -53,31 +58,42 @@ describe('getExportSettings', () => { return false; } - return 'helo world'; + return 'hello world'; }); }); test('getExportSettings: returns the expected result', async () => { - expect(await getExportSettings(uiSettingsClient, config, '', logger)).toMatchObject({ - bom: '', - checkForFormulas: true, - escapeFormulaValues: false, - includeFrozen: false, - maxConcurrentShardRequests: 5, - maxSizeBytes: 180000, - scroll: { - duration: '30s', - size: 500, - }, - separator: ',', - timezone: 'UTC', - }); + expect(await getExportSettings(uiSettingsClient, taskInstanceFields, config, '', logger)) + .toMatchInlineSnapshot(` + Object { + "bom": "", + "checkForFormulas": true, + "escapeFormulaValues": false, + "escapeValue": [Function], + "includeFrozen": false, + "maxConcurrentShardRequests": 5, + "maxSizeBytes": 180000, + "scroll": Object { + "duration": [Function], + "size": 500, + "strategy": "pit", + }, + "separator": ",", + "taskInstanceFields": Object { + "retryAt": null, + "startedAt": null, + }, + "timezone": "UTC", + } + `); }); test('does not add a default scroll strategy', async () => { // @ts-expect-error undefined isn't allowed config = { ...config, scroll: { strategy: undefined } }; - expect(await getExportSettings(uiSettingsClient, config, '', logger)).toMatchObject( + expect( + await getExportSettings(uiSettingsClient, taskInstanceFields, config, '', logger) + ).toMatchObject( expect.objectContaining({ scroll: expect.objectContaining({ strategy: undefined }) }) ); }); @@ -85,7 +101,9 @@ describe('getExportSettings', () => { test('passes the scroll=pit strategy through', async () => { config = { ...config, scroll: { ...config.scroll, strategy: 'pit' } }; - expect(await getExportSettings(uiSettingsClient, config, '', logger)).toMatchObject( + expect( + await getExportSettings(uiSettingsClient, taskInstanceFields, config, '', logger) + ).toMatchObject( expect.objectContaining({ scroll: expect.objectContaining({ strategy: 'pit' }) }) ); }); @@ -93,7 +111,9 @@ describe('getExportSettings', () => { test('passes the scroll=scroll strategy through', async () => { config = { ...config, scroll: { ...config.scroll, strategy: 'scroll' } }; - expect(await getExportSettings(uiSettingsClient, config, '', logger)).toMatchObject( + expect( + await getExportSettings(uiSettingsClient, taskInstanceFields, config, '', logger) + ).toMatchObject( expect.objectContaining({ scroll: expect.objectContaining({ strategy: 'scroll', @@ -103,7 +123,13 @@ describe('getExportSettings', () => { }); test('escapeValue function', async () => { - const { escapeValue } = await getExportSettings(uiSettingsClient, config, '', logger); + const { escapeValue } = await getExportSettings( + uiSettingsClient, + taskInstanceFields, + config, + '', + logger + ); expect(escapeValue(`test`)).toBe(`test`); expect(escapeValue(`this is, a test`)).toBe(`"this is, a test"`); expect(escapeValue(`"tet"`)).toBe(`"""tet"""`); @@ -119,7 +145,111 @@ describe('getExportSettings', () => { }); expect( - await getExportSettings(uiSettingsClient, config, '', logger).then(({ timezone }) => timezone) + await getExportSettings(uiSettingsClient, taskInstanceFields, config, '', logger).then( + ({ timezone }) => timezone + ) ).toBe(`America/Aruba`); }); + + describe('scroll duration function', () => { + let spiedDateNow: jest.Spied; + let mockedTaskInstanceFields: TaskInstanceFields; + const durationApart: Duration = { minutes: 5 }; + + beforeEach(() => { + const now = Date.now(); + + // freeze time for test + spiedDateNow = jest.spyOn(Date, 'now').mockReturnValue(now); + + mockedTaskInstanceFields = { + startedAt: sub(new Date(Date.now()), durationApart), + retryAt: add(new Date(Date.now()), durationApart), + }; + }); + + afterEach(() => { + spiedDateNow.mockRestore(); + }); + + it('returns its specified value when value is not auto', async () => { + const { scroll } = await getExportSettings( + uiSettingsClient, + taskInstanceFields, + config, + '', + logger + ); + + expect(scroll.duration(mockedTaskInstanceFields)).toBe(config.scroll.duration); + }); + + it('throws when the scroll duration config is auto and retryAt value of the taskInstanceField passed is falsy', async () => { + const configWithScrollAutoDuration = { + ...config, + scroll: { + ...config.scroll, + duration: 'auto', + }, + }; + + const { scroll } = await getExportSettings( + uiSettingsClient, + taskInstanceFields, + configWithScrollAutoDuration, + '', + logger + ); + + expect( + scroll.duration.bind(null, { startedAt: new Date(Date.now()), retryAt: null }) + ).toThrow(); + }); + + it('returns a value that is the difference of the current time from the value of retryAt provided in the passed taskInstanceFields', async () => { + const configWithScrollAutoDuration = { + ...config, + scroll: { + ...config.scroll, + duration: 'auto', + }, + }; + + const { scroll } = await getExportSettings( + uiSettingsClient, + taskInstanceFields, + configWithScrollAutoDuration, + '', + logger + ); + + expect(scroll.duration(mockedTaskInstanceFields)).toBe( + `${durationApart.minutes! * 60 * 1000}ms` + ); + }); + + it('returns 0 if current time exceeds the value of retryAt provided in the passed taskInstanceFields', async () => { + const configWithScrollAutoDuration = { + ...config, + scroll: { + ...config.scroll, + duration: 'auto', + }, + }; + + spiedDateNow.mockReturnValue( + add(mockedTaskInstanceFields.retryAt!, { minutes: 5 }).getTime() + ); + + const { scroll } = await getExportSettings( + uiSettingsClient, + taskInstanceFields, + configWithScrollAutoDuration, + '', + logger + ); + + expect(scroll.duration(mockedTaskInstanceFields)).toBe('0ms'); + }); + }); }); diff --git a/packages/kbn-generate-csv/src/lib/get_export_settings.ts b/packages/kbn-generate-csv/src/lib/get_export_settings.ts index 20830a3c3c023..a3a8b5625bff3 100644 --- a/packages/kbn-generate-csv/src/lib/get_export_settings.ts +++ b/packages/kbn-generate-csv/src/lib/get_export_settings.ts @@ -10,7 +10,7 @@ import type { ByteSizeValue } from '@kbn/config-schema'; import type { IUiSettingsClient, Logger } from '@kbn/core/server'; import { createEscapeValue } from '@kbn/data-plugin/common'; import type { ReportingConfigType } from '@kbn/reporting-server'; - +import type { TaskInstanceFields } from '@kbn/reporting-common/types'; import { CSV_BOM_CHARS, UI_SETTINGS_CSV_QUOTE_VALUES, @@ -22,10 +22,14 @@ import { CsvPagingStrategy } from '../../types'; export interface CsvExportSettings { timezone: string; + taskInstanceFields: TaskInstanceFields; scroll: { strategy?: CsvPagingStrategy; size: number; - duration: string; + /** + * compute scroll duration, duration is returned in ms by default + */ + duration: (args: TaskInstanceFields, format?: 'ms' | 's') => string; }; bom: string; separator: string; @@ -39,6 +43,7 @@ export interface CsvExportSettings { export const getExportSettings = async ( client: IUiSettingsClient, + taskInstanceFields: TaskInstanceFields, config: ReportingConfigType['csv'], timezone: string | undefined, logger: Logger @@ -75,10 +80,33 @@ export const getExportSettings = async ( return { timezone: setTimezone, + taskInstanceFields, scroll: { strategy: config.scroll.strategy as CsvPagingStrategy, size: config.scroll.size, - duration: config.scroll.duration, + duration: ({ retryAt }, format = 'ms') => { + if (config.scroll.duration !== 'auto') { + return config.scroll.duration; + } + + if (!retryAt) { + throw new Error( + 'config "xpack.reporting.csv.scroll.duration" of "auto" mandates that the task instance field passed specifies a retryAt value' + ); + } + + const now = new Date(Date.now()).getTime(); + const timeTillRetry = new Date(retryAt).getTime(); + + if (now >= timeTillRetry) { + return `0${format}`; + } + + const _duration = timeTillRetry - now; + const result = format === 'ms' ? `${_duration}ms` : `${_duration / 1000}s`; + logger.debug(`using timeout duration of ${result} for csv scroll`); + return result; + }, }, bom, includeFrozen, diff --git a/packages/kbn-generate-csv/src/lib/search_cursor.ts b/packages/kbn-generate-csv/src/lib/search_cursor.ts index 9632564492e3e..834f5d6fb2fbc 100644 --- a/packages/kbn-generate-csv/src/lib/search_cursor.ts +++ b/packages/kbn-generate-csv/src/lib/search_cursor.ts @@ -23,7 +23,7 @@ export interface SearchCursorClients { export type SearchCursorSettings = Pick< CsvExportSettings, - 'scroll' | 'includeFrozen' | 'maxConcurrentShardRequests' + 'scroll' | 'includeFrozen' | 'maxConcurrentShardRequests' | 'taskInstanceFields' >; export abstract class SearchCursor { @@ -33,6 +33,7 @@ export abstract class SearchCursor { protected indexPatternTitle: string, protected settings: SearchCursorSettings, protected clients: SearchCursorClients, + protected abortController: AbortController, protected logger: Logger ) {} diff --git a/packages/kbn-generate-csv/src/lib/search_cursor_pit.test.ts b/packages/kbn-generate-csv/src/lib/search_cursor_pit.test.ts index c76a66a983893..4fb3ccda06c63 100644 --- a/packages/kbn-generate-csv/src/lib/search_cursor_pit.test.ts +++ b/packages/kbn-generate-csv/src/lib/search_cursor_pit.test.ts @@ -24,11 +24,12 @@ describe('CSV Export Search Cursor', () => { beforeEach(async () => { settings = { scroll: { - duration: '10m', + duration: jest.fn(() => '10m'), size: 500, }, includeFrozen: false, maxConcurrentShardRequests: 5, + taskInstanceFields: { startedAt: null, retryAt: null }, }; es = elasticsearchServiceMock.createScopedClusterClient(); @@ -37,7 +38,13 @@ describe('CSV Export Search Cursor', () => { logger = loggingSystemMock.createLogger(); - cursor = new SearchCursorPit('test-index-pattern-string', settings, { data, es }, logger); + cursor = new SearchCursorPit( + 'test-index-pattern-string', + settings, + { data, es }, + new AbortController(), + logger + ); const openPointInTimeSpy = jest // @ts-expect-error create spy on private method diff --git a/packages/kbn-generate-csv/src/lib/search_cursor_pit.ts b/packages/kbn-generate-csv/src/lib/search_cursor_pit.ts index 110e15ca8a602..a7099f8419339 100644 --- a/packages/kbn-generate-csv/src/lib/search_cursor_pit.ts +++ b/packages/kbn-generate-csv/src/lib/search_cursor_pit.ts @@ -24,9 +24,10 @@ export class SearchCursorPit extends SearchCursor { indexPatternTitle: string, settings: SearchCursorSettings, clients: SearchCursorClients, + abortController: AbortController, logger: Logger ) { - super(indexPatternTitle, settings, clients, logger); + super(indexPatternTitle, settings, clients, abortController, logger); } /** @@ -37,7 +38,7 @@ export class SearchCursorPit extends SearchCursor { } private async openPointInTime() { - const { includeFrozen, maxConcurrentShardRequests, scroll } = this.settings; + const { includeFrozen, maxConcurrentShardRequests, scroll, taskInstanceFields } = this.settings; let pitId: string | undefined; @@ -47,13 +48,14 @@ export class SearchCursorPit extends SearchCursor { const response = await this.clients.es.asCurrentUser.openPointInTime( { index: this.indexPatternTitle, - keep_alive: scroll.duration, + keep_alive: scroll.duration(taskInstanceFields), ignore_unavailable: true, // @ts-expect-error ignore_throttled is not in the type definition, but it is accepted by es ignore_throttled: includeFrozen ? false : undefined, // "true" will cause deprecation warnings logged in ES }, { - requestTimeout: scroll.duration, + signal: this.abortController.signal, + requestTimeout: scroll.duration(taskInstanceFields), maxRetries: 0, maxConcurrentShardRequests, } @@ -73,7 +75,7 @@ export class SearchCursorPit extends SearchCursor { } private async searchWithPit(searchBody: SearchRequest) { - const { maxConcurrentShardRequests, scroll } = this.settings; + const { maxConcurrentShardRequests, scroll, taskInstanceFields } = this.settings; const searchParamsPit = { params: { @@ -85,22 +87,25 @@ export class SearchCursorPit extends SearchCursor { return await lastValueFrom( this.clients.data.search(searchParamsPit, { strategy: ES_SEARCH_STRATEGY, + abortSignal: this.abortController.signal, transport: { maxRetries: 0, // retrying reporting jobs is handled in the task manager scheduling logic - requestTimeout: scroll.duration, + requestTimeout: scroll.duration(taskInstanceFields), }, }) ); } public async getPage(searchSource: ISearchSource) { + const { scroll, taskInstanceFields } = this.settings; + if (!this.cursorId) { throw new Error(`No access to valid PIT ID!`); } searchSource.setField('pit', { id: this.cursorId, - keep_alive: this.settings.scroll.duration, + keep_alive: scroll.duration(taskInstanceFields), }); const searchAfter = this.getSearchAfter(); diff --git a/packages/kbn-generate-csv/src/lib/search_cursor_scroll.test.ts b/packages/kbn-generate-csv/src/lib/search_cursor_scroll.test.ts index 4b4ae4a05f4ec..6d36ee64d5761 100644 --- a/packages/kbn-generate-csv/src/lib/search_cursor_scroll.test.ts +++ b/packages/kbn-generate-csv/src/lib/search_cursor_scroll.test.ts @@ -24,10 +24,11 @@ describe('CSV Export Search Cursor', () => { beforeEach(async () => { settings = { scroll: { - duration: '10m', + duration: jest.fn(() => '10m'), size: 500, }, includeFrozen: false, + taskInstanceFields: { startedAt: null, retryAt: null }, maxConcurrentShardRequests: 5, }; @@ -37,7 +38,14 @@ describe('CSV Export Search Cursor', () => { logger = loggingSystemMock.createLogger(); - cursor = new SearchCursorScroll('test-index-pattern-string', settings, { data, es }, logger); + cursor = new SearchCursorScroll( + 'test-index-pattern-string', + settings, + { data, es }, + new AbortController(), + logger + ); + await cursor.initialize(); }); diff --git a/packages/kbn-generate-csv/src/lib/search_cursor_scroll.ts b/packages/kbn-generate-csv/src/lib/search_cursor_scroll.ts index 6e2bc2100a14a..8b8e41da39700 100644 --- a/packages/kbn-generate-csv/src/lib/search_cursor_scroll.ts +++ b/packages/kbn-generate-csv/src/lib/search_cursor_scroll.ts @@ -23,22 +23,23 @@ export class SearchCursorScroll extends SearchCursor { indexPatternTitle: string, settings: SearchCursorSettings, clients: SearchCursorClients, + abortController: AbortController, logger: Logger ) { - super(indexPatternTitle, settings, clients, logger); + super(indexPatternTitle, settings, clients, abortController, logger); } // The first search query begins the scroll context in ES public async initialize() {} private async scan(searchBody: SearchRequest) { - const { includeFrozen, maxConcurrentShardRequests, scroll } = this.settings; + const { includeFrozen, maxConcurrentShardRequests, scroll, taskInstanceFields } = this.settings; const searchParamsScan = { params: { body: searchBody, index: this.indexPatternTitle, - scroll: scroll.duration, + scroll: scroll.duration(taskInstanceFields), size: scroll.size, ignore_throttled: includeFrozen ? false : undefined, // "true" will cause deprecation warnings logged in ES max_concurrent_shard_requests: maxConcurrentShardRequests, @@ -48,21 +49,23 @@ export class SearchCursorScroll extends SearchCursor { return await lastValueFrom( this.clients.data.search(searchParamsScan, { strategy: ES_SEARCH_STRATEGY, + abortSignal: this.abortController.signal, transport: { maxRetries: 0, // retrying reporting jobs is handled in the task manager scheduling logic - requestTimeout: scroll.duration, + requestTimeout: scroll.duration(taskInstanceFields), }, }) ); } private async scroll() { - const { duration } = this.settings.scroll; + const { scroll, taskInstanceFields } = this.settings; return await this.clients.es.asCurrentUser.scroll( - { scroll: duration, scroll_id: this.cursorId }, + { scroll: scroll.duration(taskInstanceFields), scroll_id: this.cursorId }, { + signal: this.abortController.signal, maxRetries: 0, // retrying reporting jobs is handled in the task manager scheduling logic - requestTimeout: duration, + requestTimeout: scroll.duration(taskInstanceFields), } ); } diff --git a/packages/kbn-guided-onboarding/src/components/landing_page/guide/guide_card.tsx b/packages/kbn-guided-onboarding/src/components/landing_page/guide/guide_card.tsx index 1194fc5a8d259..a9ea5fbab5b6e 100644 --- a/packages/kbn-guided-onboarding/src/components/landing_page/guide/guide_card.tsx +++ b/packages/kbn-guided-onboarding/src/components/landing_page/guide/guide_card.tsx @@ -8,14 +8,7 @@ import React, { Fragment, useCallback, useState } from 'react'; -import { - EuiCard, - EuiFlexGroup, - EuiFlexItem, - EuiIcon, - EuiTextColor, - useEuiTheme, -} from '@elastic/eui'; +import { EuiCard, EuiFlexGroup, EuiIcon, EuiTextColor, useEuiTheme } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { css } from '@emotion/react'; @@ -138,7 +131,7 @@ export const GuideCard = ({ const cardCss = css` position: relative; - height: 125px; + height: 150px; width: 380px; .euiCard__top { margin-block-end: 8px; @@ -149,7 +142,7 @@ export const GuideCard = ({ } @media (min-width: 768px) and (max-width: 1210px) { max-width: 230px; - height: 175px; + height: 200px; justify-content: center; } `; @@ -175,16 +168,12 @@ export const GuideCard = ({ )} {isComplete && ( - - - - - - {i18n.translate('guidedOnboardingPackage.gettingStarted.cards.completeLabel', { - defaultMessage: 'Guide complete', - })} - - + + + {i18n.translate('guidedOnboardingPackage.gettingStarted.cards.completeLabel', { + defaultMessage: 'Guide complete', + })} + )} diff --git a/packages/kbn-management/settings/setting_ids/index.ts b/packages/kbn-management/settings/setting_ids/index.ts index 3268bc5ef05c8..b8b6fbde25add 100644 --- a/packages/kbn-management/settings/setting_ids/index.ts +++ b/packages/kbn-management/settings/setting_ids/index.ts @@ -123,6 +123,8 @@ export const OBSERVABILITY_ENABLE_COMPARISON_BY_DEFAULT_ID = 'observability:enableComparisonByDefault'; export const OBSERVABILITY_ENABLE_INFRASTRUCTURE_HOSTS_VIEW_ID = 'observability:enableInfrastructureHostsView'; +export const OBSERVABILITY_ENABLE_INFRASTRUCTURE_HOSTS_CUSTOM_DASHBOARDS_ID = + 'observability:enableInfrastructureHostsCustomDashboards'; export const OBSERVABILITY_ENABLE_INSPECT_ES_QUERIES_ID = 'observability:enableInspectEsQueries'; export const OBSERVABILITY_MAX_SUGGESTIONS_ID = 'observability:maxSuggestions'; export const OBSERVABILITY_PROFILING_ELASTICSEARCH_PLUGIN_ID = diff --git a/packages/kbn-mock-idp-plugin/server/index.ts b/packages/kbn-mock-idp-plugin/server/index.ts index 81f6713e675fe..33e5556142155 100644 --- a/packages/kbn-mock-idp-plugin/server/index.ts +++ b/packages/kbn-mock-idp-plugin/server/index.ts @@ -6,5 +6,17 @@ * Side Public License, v 1. */ +import { offeringBasedSchema, schema } from '@kbn/config-schema'; + export type { CreateSAMLResponseParams } from './plugin'; export { plugin } from './plugin'; + +export const config = { + schema: schema.object({ + // The plugin should only be enabled in Serverless. + enabled: offeringBasedSchema({ + serverless: schema.boolean({ defaultValue: true }), + options: { defaultValue: false }, + }), + }), +}; diff --git a/packages/kbn-reporting/export_types/csv/csv_v2.ts b/packages/kbn-reporting/export_types/csv/csv_v2.ts index 3bea7f03375bb..aeaee5b98d7cd 100644 --- a/packages/kbn-reporting/export_types/csv/csv_v2.ts +++ b/packages/kbn-reporting/export_types/csv/csv_v2.ts @@ -147,6 +147,7 @@ export class CsvV2ExportType extends ExportType< ...job, }, csvConfig, + taskInstanceFields, clients, cancellationToken, logger, diff --git a/packages/kbn-reporting/server/config_schema.ts b/packages/kbn-reporting/server/config_schema.ts index a823babde8f00..0c602b1bb340b 100644 --- a/packages/kbn-reporting/server/config_schema.ts +++ b/packages/kbn-reporting/server/config_schema.ts @@ -74,10 +74,10 @@ const CsvSchema = schema.object({ { defaultValue: 'pit' } ), duration: schema.string({ - defaultValue: '30s', // this value is passed directly to ES, so string only format is preferred + defaultValue: '30s', // values other than "auto" are passed directly to ES, so string only format is preferred validate(value) { - if (!/^[0-9]+(d|h|m|s|ms|micros|nanos)$/.test(value)) { - return 'must be a duration string'; + if (!/(^[0-9]+(d|h|m|s|ms|micros|nanos)|auto)$/.test(value)) { + return 'must be either "auto" or a duration string'; } }, }), diff --git a/packages/kbn-search-connectors/components/configuration/connector_configuration.tsx b/packages/kbn-search-connectors/components/configuration/connector_configuration.tsx index b3fc911460bf7..1377626f3cd58 100644 --- a/packages/kbn-search-connectors/components/configuration/connector_configuration.tsx +++ b/packages/kbn-search-connectors/components/configuration/connector_configuration.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import React, { createContext, useEffect, useState } from 'react'; +import React, { createContext, useEffect, useRef, useState } from 'react'; import { EuiButton, @@ -20,8 +20,15 @@ import { import { i18n } from '@kbn/i18n'; +import { isDeepEqual } from 'react-use/lib/util'; import { sortAndFilterConnectorConfiguration } from '../../utils/connector_configuration_utils'; -import { Connector, ConnectorConfigProperties, ConnectorStatus, FeatureName } from '../..'; +import { + Connector, + ConnectorConfigProperties, + ConnectorConfiguration, + ConnectorStatus, + FeatureName, +} from '../..'; import { ConnectorConfigurationForm } from './connector_configuration_form'; @@ -82,6 +89,7 @@ export const ConnectorConfigurationComponent: React.FC { + const configurationRef = useRef({}); const { configuration, error, @@ -95,7 +103,10 @@ export const ConnectorConfigurationComponent: React.FC { - setIsEditing(false); + if (!isDeepEqual(configuration, configurationRef.current)) { + configurationRef.current = configuration; + setIsEditing(false); + } }, [configuration]); useEffect(() => { diff --git a/packages/kbn-search-connectors/lib/update_connector_index_name.test.ts b/packages/kbn-search-connectors/lib/update_connector_index_name.test.ts new file mode 100644 index 0000000000000..6dec1b9575114 --- /dev/null +++ b/packages/kbn-search-connectors/lib/update_connector_index_name.test.ts @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ElasticsearchClient } from '@kbn/core/server'; + +import { errors } from '@elastic/elasticsearch'; + +import { updateConnectorIndexName } from './update_connector_index_name'; + +describe('updateConnectorIndexName lib function', () => { + const mockClient = { + transport: { + request: jest.fn(), + }, + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should update connector index_name', async () => { + mockClient.transport.request.mockImplementation(() => ({ result: 'updated' })); + + await expect( + updateConnectorIndexName( + mockClient as unknown as ElasticsearchClient, + 'connectorId', + 'test-index-42' + ) + ).resolves.toEqual({ result: 'updated' }); + expect(mockClient.transport.request).toHaveBeenCalledWith({ + body: { + index_name: 'test-index-42', + }, + method: 'PUT', + path: '/_connector/connectorId/_index_name', + }); + }); + + it('should not index document if there is no connector', async () => { + mockClient.transport.request.mockImplementationOnce(() => { + return Promise.reject( + new errors.ResponseError({ + statusCode: 404, + body: { + error: { + type: `document_missing_exception`, + }, + }, + } as any) + ); + }); + await expect( + updateConnectorIndexName( + mockClient as unknown as ElasticsearchClient, + 'connectorId', + 'test-index-42' + ) + ).rejects.toEqual( + new errors.ResponseError({ + statusCode: 404, + body: { + error: { + type: `document_missing_exception`, + }, + }, + } as any) + ); + }); +}); diff --git a/packages/kbn-search-connectors/lib/update_connector_index_name.ts b/packages/kbn-search-connectors/lib/update_connector_index_name.ts index 7cff11e7c6a55..254a83812c245 100644 --- a/packages/kbn-search-connectors/lib/update_connector_index_name.ts +++ b/packages/kbn-search-connectors/lib/update_connector_index_name.ts @@ -6,29 +6,19 @@ * Side Public License, v 1. */ -import { WriteResponseBase } from '@elastic/elasticsearch/lib/api/types'; +import { Result } from '@elastic/elasticsearch/lib/api/types'; import { ElasticsearchClient } from '@kbn/core/server'; -import { i18n } from '@kbn/i18n'; -import { CONNECTORS_INDEX, fetchConnectorByIndexName } from '..'; export const updateConnectorIndexName = async ( client: ElasticsearchClient, connectorId: string, indexName: string -): Promise => { - const connectorResult = await fetchConnectorByIndexName(client, indexName); - if (connectorResult) { - throw new Error( - i18n.translate('searchConnectors.server.connectors.indexName.error', { - defaultMessage: - 'This index has already been registered to connector {connectorId}. Please delete that connector or select a different index name.', - values: { connectorId }, - }) - ); - } - return await client.update({ - index: CONNECTORS_INDEX, - doc: { index_name: indexName }, - id: connectorId, +): Promise => { + return await client.transport.request({ + method: 'PUT', + path: `/_connector/${connectorId}/_index_name`, + body: { + index_name: indexName, + }, }); }; diff --git a/packages/kbn-search-connectors/lib/update_connector_service_type.test.ts b/packages/kbn-search-connectors/lib/update_connector_service_type.test.ts new file mode 100644 index 0000000000000..9a452e982430d --- /dev/null +++ b/packages/kbn-search-connectors/lib/update_connector_service_type.test.ts @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ElasticsearchClient } from '@kbn/core/server'; + +import { errors } from '@elastic/elasticsearch'; + +import { updateConnectorServiceType } from './update_connector_service_type'; + +describe('updateConnectorServiceType lib function', () => { + const mockClient = { + transport: { + request: jest.fn(), + }, + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should update connector service type', async () => { + mockClient.transport.request.mockImplementation(() => ({ result: 'updated' })); + + const connectorId = 'connectorId'; + const serviceType = 'new-service-type'; + + const result = await updateConnectorServiceType( + mockClient as unknown as ElasticsearchClient, + connectorId, + serviceType + ); + expect(result).toEqual({ result: 'updated' }); + + expect(mockClient.transport.request).toHaveBeenNthCalledWith(1, { + method: 'PUT', + path: `/_connector/${connectorId}/_configuration`, + body: { + configuration: {}, + }, + }); + expect(mockClient.transport.request).toHaveBeenNthCalledWith(2, { + method: 'PUT', + path: `/_connector/${connectorId}/_service_type`, + body: { + service_type: serviceType, + }, + }); + expect(mockClient.transport.request).toHaveBeenCalledTimes(2); + }); + + it('should not index document if there is no connector', async () => { + mockClient.transport.request.mockImplementationOnce(() => { + return Promise.reject( + new errors.ResponseError({ + statusCode: 404, + body: { + error: { + type: `document_missing_exception`, + }, + }, + } as any) + ); + }); + await expect( + updateConnectorServiceType( + mockClient as unknown as ElasticsearchClient, + 'connectorId', + 'new-service-type' + ) + ).rejects.toEqual( + new errors.ResponseError({ + statusCode: 404, + body: { + error: { + type: `document_missing_exception`, + }, + }, + } as any) + ); + }); +}); diff --git a/packages/kbn-search-connectors/lib/update_connector_service_type.ts b/packages/kbn-search-connectors/lib/update_connector_service_type.ts index 3040918a75b95..70d1105e69c3c 100644 --- a/packages/kbn-search-connectors/lib/update_connector_service_type.ts +++ b/packages/kbn-search-connectors/lib/update_connector_service_type.ts @@ -6,42 +6,29 @@ * Side Public License, v 1. */ +import { Result } from '@elastic/elasticsearch/lib/api/types'; import { ElasticsearchClient } from '@kbn/core/server'; -import { i18n } from '@kbn/i18n'; - -import { CONNECTORS_INDEX, fetchConnectorById } from '..'; - -import { ConnectorDocument, ConnectorStatus } from '../types/connectors'; export const updateConnectorServiceType = async ( client: ElasticsearchClient, connectorId: string, serviceType: string ) => { - const connectorResult = await fetchConnectorById(client, connectorId); - - if (connectorResult?.value) { - const result = await client.index({ - document: { - ...connectorResult.value, - configuration: {}, - service_type: serviceType, - status: - connectorResult.value.status === ConnectorStatus.CREATED - ? ConnectorStatus.CREATED - : ConnectorStatus.NEEDS_CONFIGURATION, - }, - id: connectorId, - index: CONNECTORS_INDEX, - if_seq_no: connectorResult.seqNo, - if_primary_term: connectorResult.primaryTerm, - }); - return result; - } else { - throw new Error( - i18n.translate('searchConnectors.server.connectors.serviceType.error', { - defaultMessage: 'Could not find document', - }) - ); - } + // First clear connector configuration + await client.transport.request({ + method: 'PUT', + path: `/_connector/${connectorId}/_configuration`, + body: { + configuration: {}, + }, + }); + // Then update service type, on startup connector framework + // will populate missing config fields + return await client.transport.request({ + method: 'PUT', + path: `/_connector/${connectorId}/_service_type`, + body: { + service_type: serviceType, + }, + }); }; diff --git a/packages/kbn-search-connectors/lib/update_connector_status.test.ts b/packages/kbn-search-connectors/lib/update_connector_status.test.ts new file mode 100644 index 0000000000000..522c0c2d8991f --- /dev/null +++ b/packages/kbn-search-connectors/lib/update_connector_status.test.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 { ElasticsearchClient } from '@kbn/core/server'; + +import { errors } from '@elastic/elasticsearch'; + +import { updateConnectorStatus } from './update_connector_status'; +import { ConnectorStatus } from '../types/connectors'; + +describe('updateConnectorStatus lib function', () => { + const mockClient = { + transport: { + request: jest.fn(), + }, + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should update connector index_name', async () => { + mockClient.transport.request.mockImplementation(() => ({ result: 'updated' })); + + await expect( + updateConnectorStatus( + mockClient as unknown as ElasticsearchClient, + 'connectorId', + ConnectorStatus.CONFIGURED + ) + ).resolves.toEqual({ result: 'updated' }); + expect(mockClient.transport.request).toHaveBeenCalledWith({ + body: { + status: 'configured', + }, + method: 'PUT', + path: '/_connector/connectorId/_status', + }); + }); + + it('should not index document if there is no connector', async () => { + mockClient.transport.request.mockImplementationOnce(() => { + return Promise.reject( + new errors.ResponseError({ + statusCode: 404, + body: { + error: { + type: `document_missing_exception`, + }, + }, + } as any) + ); + }); + await expect( + updateConnectorStatus( + mockClient as unknown as ElasticsearchClient, + 'connectorId', + ConnectorStatus.CONFIGURED + ) + ).rejects.toEqual( + new errors.ResponseError({ + statusCode: 404, + body: { + error: { + type: `document_missing_exception`, + }, + }, + } as any) + ); + }); +}); diff --git a/packages/kbn-search-connectors/lib/update_connector_status.ts b/packages/kbn-search-connectors/lib/update_connector_status.ts index 42ba9adb4f9e0..cff5927ce6ae9 100644 --- a/packages/kbn-search-connectors/lib/update_connector_status.ts +++ b/packages/kbn-search-connectors/lib/update_connector_status.ts @@ -6,36 +6,21 @@ * Side Public License, v 1. */ +import { Result } from '@elastic/elasticsearch/lib/api/types'; import { ElasticsearchClient } from '@kbn/core/server'; -import { i18n } from '@kbn/i18n'; -import { CONNECTORS_INDEX } from '..'; - -import { ConnectorDocument, ConnectorStatus } from '../types/connectors'; +import { ConnectorStatus } from '../types/connectors'; export const updateConnectorStatus = async ( client: ElasticsearchClient, connectorId: string, status: ConnectorStatus ) => { - const connectorResult = await client.get({ - id: connectorId, - index: CONNECTORS_INDEX, + return await client.transport.request({ + method: 'PUT', + path: `/_connector/${connectorId}/_status`, + body: { + status, + }, }); - const connector = connectorResult._source; - if (connector) { - const result = await client.index({ - document: { ...connector, status }, - id: connectorId, - index: CONNECTORS_INDEX, - }); - await client.indices.refresh({ index: CONNECTORS_INDEX }); - return result; - } else { - throw new Error( - i18n.translate('searchConnectors.server.connectors.serviceType.error', { - defaultMessage: 'Could not find document', - }) - ); - } }; diff --git a/packages/kbn-search-connectors/lib/update_native.test.ts b/packages/kbn-search-connectors/lib/update_native.test.ts new file mode 100644 index 0000000000000..b0c87b919b514 --- /dev/null +++ b/packages/kbn-search-connectors/lib/update_native.test.ts @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ElasticsearchClient } from '@kbn/core/server'; + +import { errors } from '@elastic/elasticsearch'; + +import { putUpdateNative } from './update_native'; + +describe('putUpdateNative lib function', () => { + const mockClient = { + transport: { + request: jest.fn(), + }, + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should update connector is_native flag', async () => { + mockClient.transport.request.mockImplementation(() => ({ result: 'updated' })); + + await expect( + putUpdateNative(mockClient as unknown as ElasticsearchClient, 'connectorId', true) + ).resolves.toEqual({ result: 'updated' }); + expect(mockClient.transport.request).toHaveBeenCalledWith({ + body: { + is_native: true, + }, + method: 'PUT', + path: '/_connector/connectorId/_native', + }); + }); + + it('should not index document if there is no connector', async () => { + mockClient.transport.request.mockImplementationOnce(() => { + return Promise.reject( + new errors.ResponseError({ + statusCode: 404, + body: { + error: { + type: `document_missing_exception`, + }, + }, + } as any) + ); + }); + await expect( + putUpdateNative(mockClient as unknown as ElasticsearchClient, 'connectorId', true) + ).rejects.toEqual( + new errors.ResponseError({ + statusCode: 404, + body: { + error: { + type: `document_missing_exception`, + }, + }, + } as any) + ); + }); +}); diff --git a/packages/kbn-search-connectors/lib/update_native.ts b/packages/kbn-search-connectors/lib/update_native.ts index 73af04e75aa8b..4f5bf4e66613f 100644 --- a/packages/kbn-search-connectors/lib/update_native.ts +++ b/packages/kbn-search-connectors/lib/update_native.ts @@ -6,24 +6,21 @@ * Side Public License, v 1. */ -import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { Result } from '@elastic/elasticsearch/lib/api/types'; -import { CONNECTORS_INDEX } from '..'; -import { Connector, ConnectorStatus } from '../types/connectors'; +import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; export const putUpdateNative = async ( client: ElasticsearchClient, connectorId: string, isNative: boolean ) => { - const result = await client.update({ - doc: { + const result = await client.transport.request({ + method: 'PUT', + path: `/_connector/${connectorId}/_native`, + body: { is_native: isNative, - status: ConnectorStatus.CONFIGURED, }, - id: connectorId, - index: CONNECTORS_INDEX, }); - return result; }; diff --git a/packages/kbn-securitysolution-grouping/src/hooks/use_get_group_selector.test.tsx b/packages/kbn-securitysolution-grouping/src/hooks/use_get_group_selector.test.tsx index e6221c677b9d5..1ebd2b5a18174 100644 --- a/packages/kbn-securitysolution-grouping/src/hooks/use_get_group_selector.test.tsx +++ b/packages/kbn-securitysolution-grouping/src/hooks/use_get_group_selector.test.tsx @@ -237,6 +237,26 @@ describe('Group Selector Hooks', () => { ); }); + it('On group change, unselected group, does not sends telemetry', () => { + const testGroup = { + [groupingId]: { + ...defaultGroup, + options: defaultGroupingOptions, + activeGroups: ['host.name', customField], + }, + }; + const { result } = renderHook((props) => useGetGroupSelector(props), { + initialProps: { + ...defaultArgs, + groupingState: { + groupById: testGroup, + }, + }, + }); + act(() => result.current.props.onGroupChange(customField)); + expect(defaultArgs.tracker).not.toHaveBeenCalled(); + }); + it('On group change, executes callback', () => { const testGroup = { [groupingId]: { @@ -258,6 +278,32 @@ describe('Group Selector Hooks', () => { expect(defaultArgs.onGroupChange).toHaveBeenCalledWith({ tableId: groupingId, groupByField: customField, + groupByFields: ['host.name', customField], + }); + }); + + it('On group change, unselected group, executes callback', () => { + const testGroup = { + [groupingId]: { + ...defaultGroup, + options: defaultGroupingOptions, + activeGroups: ['host.name', customField], + }, + }; + const { result } = renderHook((props) => useGetGroupSelector(props), { + initialProps: { + ...defaultArgs, + groupingState: { + groupById: testGroup, + }, + }, + }); + act(() => result.current.props.onGroupChange(customField)); + expect(defaultArgs.onGroupChange).toHaveBeenCalledTimes(1); + expect(defaultArgs.onGroupChange).toHaveBeenCalledWith({ + tableId: groupingId, + groupByField: customField, + groupByFields: ['host.name'], }); }); diff --git a/packages/kbn-securitysolution-grouping/src/hooks/use_get_group_selector.tsx b/packages/kbn-securitysolution-grouping/src/hooks/use_get_group_selector.tsx index d3e518a48fe59..bec790ca8cce0 100644 --- a/packages/kbn-securitysolution-grouping/src/hooks/use_get_group_selector.tsx +++ b/packages/kbn-securitysolution-grouping/src/hooks/use_get_group_selector.tsx @@ -24,7 +24,11 @@ export interface UseGetGroupSelectorArgs { groupingState: GroupMap; maxGroupingLevels?: number; onOptionsChange?: (newOptions: GroupOption[]) => void; - onGroupChange?: (param: { groupByField: string; tableId: string }) => void; + onGroupChange?: (param: { + groupByField: string; + groupByFields: string[]; + tableId: string; + }) => void; tracker?: ( type: UiCounterMetricType, event: string | string[], @@ -112,33 +116,46 @@ export const useGetGroupSelector = ({ const onChange = useCallback( (groupSelection: string) => { + let newSelectedGroups: string[] = []; + let sendTelemetry = true; // Simulate a toggle behavior when maxGroupingLevels is 1 if (maxGroupingLevels === 1) { - setSelectedGroups([groupSelection]); + newSelectedGroups = [groupSelection]; } else { + // if the group is already selected, remove it if (selectedGroups.find((selected) => selected === groupSelection)) { + sendTelemetry = false; const groups = selectedGroups.filter((selectedGroup) => selectedGroup !== groupSelection); if (groups.length === 0) { - setSelectedGroups(['none']); + newSelectedGroups = ['none']; } else { - setSelectedGroups(groups); + newSelectedGroups = groups; } - return; + } else { + newSelectedGroups = isNoneGroup([groupSelection]) + ? [groupSelection] + : [ + ...selectedGroups.filter((selectedGroup) => selectedGroup !== 'none'), + groupSelection, + ]; } - - const newSelectedGroups = isNoneGroup([groupSelection]) - ? [groupSelection] - : [...selectedGroups.filter((selectedGroup) => selectedGroup !== 'none'), groupSelection]; - setSelectedGroups(newSelectedGroups); } - // built-in telemetry: UI-counter - tracker?.( - METRIC_TYPE.CLICK, - getTelemetryEvent.groupChanged({ groupingId, selected: groupSelection }) - ); + setSelectedGroups(newSelectedGroups); - onGroupChange?.({ tableId: groupingId, groupByField: groupSelection }); + if (sendTelemetry) { + // built-in telemetry: UI-counter + tracker?.( + METRIC_TYPE.CLICK, + getTelemetryEvent.groupChanged({ groupingId, selected: groupSelection }) + ); + } + + onGroupChange?.({ + tableId: groupingId, + groupByField: groupSelection, + groupByFields: newSelectedGroups, + }); }, [groupingId, maxGroupingLevels, onGroupChange, selectedGroups, setSelectedGroups, tracker] ); diff --git a/packages/kbn-securitysolution-grouping/src/hooks/use_grouping.tsx b/packages/kbn-securitysolution-grouping/src/hooks/use_grouping.tsx index 3ff2232acbd72..38ada10a74b9b 100644 --- a/packages/kbn-securitysolution-grouping/src/hooks/use_grouping.tsx +++ b/packages/kbn-securitysolution-grouping/src/hooks/use_grouping.tsx @@ -65,7 +65,11 @@ interface GroupingArgs { /** for tracking * @param param { groupByField: string; tableId: string } selected group and table id */ - onGroupChange?: (param: { groupByField: string; tableId: string }) => void; + onGroupChange?: (param: { + groupByField: string; + groupByFields: string[]; + tableId: string; + }) => void; onOptionsChange?: (options: GroupOption[]) => void; tracker?: ( type: UiCounterMetricType, diff --git a/packages/presentation/presentation_containers/interfaces/last_saved_state.ts b/packages/presentation/presentation_containers/interfaces/last_saved_state.ts index 0c7e83aef7bf3..7e9519dcbe315 100644 --- a/packages/presentation/presentation_containers/interfaces/last_saved_state.ts +++ b/packages/presentation/presentation_containers/interfaces/last_saved_state.ts @@ -30,7 +30,7 @@ export const getLastSavedStateSubjectForChild = StateType ): PublishingSubject | undefined => { if (!parentApi) return; - const fetchUnsavedChanges = (): StateType | undefined => { + const fetchLastSavedState = (): StateType | undefined => { if (!apiPublishesLastSavedState(parentApi)) return; const rawLastSavedState = parentApi.getLastSavedStateForChild(childId); if (rawLastSavedState === undefined) return; @@ -39,11 +39,11 @@ export const getLastSavedStateSubjectForChild = (fetchUnsavedChanges()); + const lastSavedStateForChild = new BehaviorSubject(fetchLastSavedState()); if (!apiPublishesLastSavedState(parentApi)) return; parentApi.lastSavedState .pipe( - map(() => fetchUnsavedChanges()), + map(() => fetchLastSavedState()), filter((rawLastSavedState) => rawLastSavedState !== undefined) ) .subscribe(lastSavedStateForChild); diff --git a/packages/presentation/presentation_containers/interfaces/presentation_container.ts b/packages/presentation/presentation_containers/interfaces/presentation_container.ts index c5c558dfe6227..a92c5af7cbd0a 100644 --- a/packages/presentation/presentation_containers/interfaces/presentation_container.ts +++ b/packages/presentation/presentation_containers/interfaces/presentation_container.ts @@ -11,11 +11,15 @@ import { PublishesLastSavedState } from './last_saved_state'; export interface PanelPackage { panelType: string; - initialState: unknown; + initialState?: object; } export type PresentationContainer = Partial & PublishesLastSavedState & { + addNewPanel: ( + panel: PanelPackage, + displaySuccessMessage?: boolean + ) => Promise; registerPanelApi: ( panelId: string, panelApi: ApiType @@ -31,7 +35,8 @@ export const apiIsPresentationContainer = ( return Boolean( (unknownApi as PresentationContainer)?.removePanel !== undefined && (unknownApi as PresentationContainer)?.registerPanelApi !== undefined && - (unknownApi as PresentationContainer)?.replacePanel !== undefined + (unknownApi as PresentationContainer)?.replacePanel !== undefined && + (unknownApi as PresentationContainer)?.addNewPanel !== undefined ); }; diff --git a/packages/presentation/presentation_containers/mocks.ts b/packages/presentation/presentation_containers/mocks.ts index 5ac45fc6049c2..6d4610075c9d5 100644 --- a/packages/presentation/presentation_containers/mocks.ts +++ b/packages/presentation/presentation_containers/mocks.ts @@ -11,9 +11,10 @@ import { PresentationContainer } from './interfaces/presentation_container'; export const getMockPresentationContainer = (): PresentationContainer => { return { - registerPanelApi: jest.fn(), removePanel: jest.fn(), + addNewPanel: jest.fn(), replacePanel: jest.fn(), + registerPanelApi: jest.fn(), lastSavedState: new Subject(), getLastSavedStateForChild: jest.fn(), }; diff --git a/packages/presentation/presentation_publishing/index.ts b/packages/presentation/presentation_publishing/index.ts index 61209d6635729..80d2c5870efbe 100644 --- a/packages/presentation/presentation_publishing/index.ts +++ b/packages/presentation/presentation_publishing/index.ts @@ -7,6 +7,10 @@ */ export interface EmbeddableApiContext { + /** + * TODO: once all actions are entirely decoupled from the embeddable system, this key should be renamed to "api" + * to reflect the fact that this context could contain any api. + */ embeddable: unknown; } diff --git a/packages/serverless/settings/observability_project/index.ts b/packages/serverless/settings/observability_project/index.ts index cae05789e3116..c95c044614e51 100644 --- a/packages/serverless/settings/observability_project/index.ts +++ b/packages/serverless/settings/observability_project/index.ts @@ -28,4 +28,5 @@ export const OBSERVABILITY_PROJECT_SETTINGS = [ settings.OBSERVABILITY_ENABLE_AWS_LAMBDA_METRICS_ID, settings.OBSERVABILITY_APM_ENABLE_CRITICAL_PATH_ID, settings.OBSERVABILITY_ENABLE_INFRASTRUCTURE_HOSTS_VIEW_ID, + settings.OBSERVABILITY_ENABLE_INFRASTRUCTURE_HOSTS_CUSTOM_DASHBOARDS_ID, ]; diff --git a/packages/shared-ux/code_editor/impl/BUILD.bazel b/packages/shared-ux/code_editor/impl/BUILD.bazel index ad571cb379afd..24f18820496a4 100644 --- a/packages/shared-ux/code_editor/impl/BUILD.bazel +++ b/packages/shared-ux/code_editor/impl/BUILD.bazel @@ -25,7 +25,6 @@ BUNDLER_DEPS = [ "@npm//react", "@npm//tslib", "@npm//react-monaco-editor", - "@npm//react-resize-detector", ] js_library( diff --git a/packages/shared-ux/code_editor/impl/code_editor.stories.tsx b/packages/shared-ux/code_editor/impl/code_editor.stories.tsx index 38c063e4ebe2b..e4a78b328cbe3 100644 --- a/packages/shared-ux/code_editor/impl/code_editor.stories.tsx +++ b/packages/shared-ux/code_editor/impl/code_editor.stories.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import React from 'react'; +import React, { useState } from 'react'; import { action } from '@storybook/addon-actions'; import { monaco as monacoEditor } from '@kbn/monaco'; @@ -32,7 +32,13 @@ const argTypes = mock.getArgumentTypes(); export const Basic = (params: CodeEditorStorybookParams) => { return ( - + ); }; @@ -199,3 +205,39 @@ export const HoverProvider = () => {
); }; + +export const AutomaticResize = (params: CodeEditorStorybookParams) => { + return ( +
+ +
+ ); +}; + +AutomaticResize.argTypes = argTypes; + +export const FitToContent = (params: CodeEditorStorybookParams) => { + const [value, setValue] = useState('hello'); + return ( + { + setValue(newValue); + action('on change'); + }} + value={value} + fitToContent={{ minLines: 3, maxLines: 5 }} + options={{ automaticLayout: true }} + /> + ); +}; + +FitToContent.argTypes = argTypes; diff --git a/packages/shared-ux/code_editor/impl/code_editor.tsx b/packages/shared-ux/code_editor/impl/code_editor.tsx index b41906d5ed456..286c3af06e0b2 100644 --- a/packages/shared-ux/code_editor/impl/code_editor.tsx +++ b/packages/shared-ux/code_editor/impl/code_editor.tsx @@ -7,7 +7,6 @@ */ import React, { useState, useRef, useCallback, useMemo, useEffect, KeyboardEvent } from 'react'; -import { useResizeDetector } from 'react-resize-detector'; import ReactMonacoEditor, { type MonacoEditorProps as ReactMonacoEditorProps, } from 'react-monaco-editor'; @@ -140,12 +139,21 @@ export interface CodeEditorProps { * Alternate text to display, when an attempt is made to edit read only content. (Defaults to "Cannot edit in read-only editor") */ readOnlyMessage?: string; + + /** + * Enables the editor to grow vertically to fit its content. + * This option overrides the `height` option. + */ + fitToContent?: { + minLines?: number; + maxLines?: number; + }; } export const CodeEditor: React.FC = ({ languageId, value, - onChange, + onChange: _onChange, width, height, options, @@ -168,6 +176,7 @@ export const CodeEditor: React.FC = ({ readOnlyMessage = i18n.translate('sharedUXPackages.codeEditor.readOnlyMessage', { defaultMessage: 'Cannot edit in read-only editor', }), + fitToContent, }) => { const { colorMode, euiTheme } = useEuiTheme(); const useDarkTheme = useDarkThemeProp ?? colorMode === 'DARK'; @@ -189,7 +198,7 @@ export const CodeEditor: React.FC = ({ const isReadOnly = options?.readOnly ?? false; - const _editor = useRef(null); + const [_editor, setEditor] = useState(null); const _placeholderWidget = useRef(null); const isSuggestionMenuOpen = useRef(false); const editorHint = useRef(null); @@ -197,21 +206,12 @@ export const CodeEditor: React.FC = ({ const [isHintActive, setIsHintActive] = useState(true); - const _updateDimensions = useCallback(() => { - _editor.current?.layout(); - }, []); - - useResizeDetector({ - handleWidth: true, - handleHeight: true, - onResize: _updateDimensions, - refreshMode: 'debounce', - }); + const onChange = useBug175684OnChange(_onChange); const startEditing = useCallback(() => { setIsHintActive(false); - _editor.current?.focus(); - }, []); + _editor?.focus(); + }, [_editor]); const stopEditing = useCallback(() => { setIsHintActive(true); @@ -391,8 +391,6 @@ export const CodeEditor: React.FC = ({ remeasureFonts(); - _editor.current = editor; - const textbox = editor.getDomNode()?.getElementsByTagName('textarea')[0]; if (textbox) { // Make sure the textarea is not directly accessible with TAB @@ -435,6 +433,7 @@ export const CodeEditor: React.FC = ({ } editorDidMount?.(editor); + setEditor(editor); }, [editorDidMount, onBlurMonaco, onKeydownMonaco, readOnlyMessage] ); @@ -454,16 +453,18 @@ export const CodeEditor: React.FC = ({ }, []); useEffect(() => { - if (placeholder && !value && _editor.current) { + if (placeholder && !value && _editor) { // Mounts editor inside constructor - _placeholderWidget.current = new PlaceholderWidget(placeholder, euiTheme, _editor.current); + _placeholderWidget.current = new PlaceholderWidget(placeholder, euiTheme, _editor); } return () => { _placeholderWidget.current?.dispose(); _placeholderWidget.current = null; }; - }, [placeholder, value, euiTheme]); + }, [placeholder, value, euiTheme, _editor]); + + useFitToContent({ editor: _editor, fitToContent, isFullScreen }); const { CopyButton } = useCopy({ isCopyable, value }); @@ -512,7 +513,7 @@ export const CodeEditor: React.FC = ({ value={value} onChange={onChange} width={isFullScreen ? '100vw' : width} - height={isFullScreen ? '100vh' : height} + height={isFullScreen ? '100vh' : fitToContent ? undefined : height} editorWillMount={_editorWillMount} editorDidMount={_editorDidMount} editorWillUnmount={_editorWillUnmount} @@ -640,3 +641,57 @@ const useCopy = ({ isCopyable, value }: { isCopyable: boolean; value: string }) return { showCopyButton, CopyButton }; }; + +const useFitToContent = ({ + editor, + fitToContent, + isFullScreen, +}: { + editor: monaco.editor.IStandaloneCodeEditor | null; + isFullScreen: boolean; + fitToContent?: { minLines?: number; maxLines?: number }; +}) => { + const isFitToContent = !!fitToContent; + const minLines = fitToContent?.minLines; + const maxLines = fitToContent?.maxLines; + useEffect(() => { + if (!editor) return; + if (isFullScreen) return; + if (!isFitToContent) return; + + const updateHeight = () => { + const contentHeight = editor.getContentHeight(); + const lineHeight = editor.getOption(monaco.editor.EditorOption.lineHeight); + const minHeight = (minLines ?? 1) * lineHeight; + let maxHeight = maxLines ? maxLines * lineHeight : contentHeight; + maxHeight = Math.max(minHeight, maxHeight); + editor.layout({ + height: Math.min(maxHeight, Math.max(minHeight, contentHeight)), + width: editor.getLayoutInfo().width, + }); + }; + updateHeight(); + const disposable = editor.onDidContentSizeChange(updateHeight); + return () => { + disposable.dispose(); + editor.layout(); // reset the layout that was controlled by the fitToContent + }; + }, [editor, isFitToContent, minLines, maxLines, isFullScreen]); +}; + +// https://github.com/elastic/kibana/issues/175684 +// 'react-monaco-editor' has a bug that it always calls the initial onChange callback, so the closure might become stale +// we work this around by calling the latest onChange from props +const useBug175684OnChange = (onChange: CodeEditorProps['onChange']) => { + const onChangePropRef = useRef(onChange); + useEffect(() => { + onChangePropRef.current = onChange; + }, [onChange]); + const onChangeWrapper = useCallback>((_value, event) => { + if (onChangePropRef.current) { + onChangePropRef.current(_value, event); + } + }, []); + + return onChangeWrapper; +}; diff --git a/packages/shared-ux/code_editor/mocks/monaco_mock/index.tsx b/packages/shared-ux/code_editor/mocks/monaco_mock/index.tsx index f5b2fc9ab4108..d9b2d4093e67f 100644 --- a/packages/shared-ux/code_editor/mocks/monaco_mock/index.tsx +++ b/packages/shared-ux/code_editor/mocks/monaco_mock/index.tsx @@ -106,7 +106,9 @@ export const MockedMonacoEditor = ({ className?: string; ['data-test-subj']?: string; }) => { - editorWillMount?.(monaco); + useComponentWillMount(() => { + editorWillMount?.(monaco); + }); useEffect(() => { editorDidMount?.( @@ -133,3 +135,11 @@ export const MockedMonacoEditor = ({ ); }; + +const useComponentWillMount = (cb: Function) => { + const willMount = React.useRef(true); + + if (willMount.current) cb(); + + willMount.current = false; +}; diff --git a/src/cli/serve/serve.js b/src/cli/serve/serve.js index 82dafb1ea3a12..ef1092ad892c0 100644 --- a/src/cli/serve/serve.js +++ b/src/cli/serve/serve.js @@ -112,12 +112,11 @@ export function applyConfigOverrides(rawConfig, opts, extraCliOptions) { if (opts.serverless) { setServerlessKibanaDevServiceAccountIfPossible(get, set, opts); - // Load mock identity provider plugin and configure realm if supported (ES only supports SAML when run with SSL) + // Configure realm if supported (ES only supports SAML when run with SSL) if (opts.ssl && MOCK_IDP_PLUGIN_SUPPORTED) { // Ensure the plugin is loaded in dynamically to exclude from production build // eslint-disable-next-line import/no-dynamic-require const { MOCK_IDP_REALM_NAME } = require(MOCK_IDP_PLUGIN_PATH); - const pluginPath = resolve(require.resolve(MOCK_IDP_PLUGIN_PATH), '..'); if (has('server.basePath')) { console.log( @@ -126,7 +125,6 @@ export function applyConfigOverrides(rawConfig, opts, extraCliOptions) { _.unset(rawConfig, 'server.basePath'); } - set('plugins.paths', _.compact([].concat(get('plugins.paths'), pluginPath))); set(`xpack.security.authc.providers.saml.${MOCK_IDP_REALM_NAME}`, { order: Number.MAX_SAFE_INTEGER, realm: MOCK_IDP_REALM_NAME, diff --git a/src/core/server/integration_tests/core_app/bundle_routes.test.ts b/src/core/server/integration_tests/core_app/bundle_routes.test.ts index af3782b015912..b53bc07a85492 100644 --- a/src/core/server/integration_tests/core_app/bundle_routes.test.ts +++ b/src/core/server/integration_tests/core_app/bundle_routes.test.ts @@ -17,7 +17,7 @@ import { HttpService } from '@kbn/core-http-server-internal'; import { createHttpServer } from '@kbn/core-http-server-mocks'; import { registerRouteForBundle, FileHashCache } from '@kbn/core-apps-server-internal'; -const buildNum = 1234; +const buildHash = 'buildHash'; const fooPluginFixture = resolve(__dirname, './__fixtures__/plugin/foo'); describe('bundle routes', () => { @@ -47,8 +47,8 @@ describe('bundle routes', () => { isDist, fileHashCache, bundlesPath: fooPluginFixture, - routePath: `/${buildNum}/bundles/plugin/foo/`, - publicPath: `/${buildNum}/bundles/plugin/foo/`, + routePath: `/${buildHash}/bundles/plugin/foo/`, + publicPath: `/${buildHash}/bundles/plugin/foo/`, }); }; @@ -62,7 +62,7 @@ describe('bundle routes', () => { await server.start(); const response = await supertest(innerServer.listener) - .get(`/${buildNum}/bundles/plugin/foo/image.png`) + .get(`/${buildHash}/bundles/plugin/foo/image.png`) .expect(200); const actualImage = await readFile(resolve(fooPluginFixture, 'image.png')); @@ -80,7 +80,7 @@ describe('bundle routes', () => { await server.start(); const response = await supertest(innerServer.listener) - .get(`/${buildNum}/bundles/plugin/foo/plugin.js`) + .get(`/${buildHash}/bundles/plugin/foo/plugin.js`) .expect(200); const actualFile = await readFile(resolve(fooPluginFixture, 'plugin.js')); @@ -98,7 +98,7 @@ describe('bundle routes', () => { await server.start(); await supertest(innerServer.listener) - .get(`/${buildNum}/bundles/plugin/foo/../outside_output.js`) + .get(`/${buildHash}/bundles/plugin/foo/../outside_output.js`) .expect(404); }); @@ -112,7 +112,7 @@ describe('bundle routes', () => { await server.start(); await supertest(innerServer.listener) - .get(`/${buildNum}/bundles/plugin/foo/missing.js`) + .get(`/${buildHash}/bundles/plugin/foo/missing.js`) .expect(404); }); @@ -126,7 +126,7 @@ describe('bundle routes', () => { await server.start(); const response = await supertest(innerServer.listener) - .get(`/${buildNum}/bundles/plugin/foo/gzip_chunk.js`) + .get(`/${buildHash}/bundles/plugin/foo/gzip_chunk.js`) .expect(200); expect(response.get('content-encoding')).toEqual('gzip'); @@ -151,7 +151,7 @@ describe('bundle routes', () => { await server.start(); const response = await supertest(innerServer.listener) - .get(`/${buildNum}/bundles/plugin/foo/gzip_chunk.js`) + .get(`/${buildHash}/bundles/plugin/foo/gzip_chunk.js`) .expect(200); expect(response.get('cache-control')).toEqual('max-age=31536000'); @@ -170,7 +170,7 @@ describe('bundle routes', () => { await server.start(); const response = await supertest(innerServer.listener) - .get(`/${buildNum}/bundles/plugin/foo/gzip_chunk.js`) + .get(`/${buildHash}/bundles/plugin/foo/gzip_chunk.js`) .expect(200); expect(response.get('cache-control')).toEqual('must-revalidate'); diff --git a/src/core/server/integration_tests/http/http_server.test.ts b/src/core/server/integration_tests/http/http_server.test.ts index eeb6b46c9ff46..f58bfba11186d 100644 --- a/src/core/server/integration_tests/http/http_server.test.ts +++ b/src/core/server/integration_tests/http/http_server.test.ts @@ -11,19 +11,21 @@ import supertest from 'supertest'; import moment from 'moment'; import { of } from 'rxjs'; import { ByteSizeValue } from '@kbn/config-schema'; -import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; import { Router } from '@kbn/core-http-router-server-internal'; import { HttpServer, HttpConfig } from '@kbn/core-http-server-internal'; +import { mockCoreContext } from '@kbn/core-base-server-mocks'; +import type { Logger } from '@kbn/logging'; describe('Http server', () => { let server: HttpServer; let config: HttpConfig; - let logger: ReturnType; + let logger: Logger; + let coreContext: ReturnType; const enhanceWithContext = (fn: (...args: any[]) => any) => fn.bind(null, {}); beforeEach(() => { - const loggingService = loggingSystemMock.create(); - logger = loggingSystemMock.createLogger(); + coreContext = mockCoreContext.create(); + logger = coreContext.logger.get(); config = { name: 'kibana', @@ -43,7 +45,7 @@ describe('Http server', () => { shutdownTimeout: moment.duration(5, 's'), } as any; - server = new HttpServer(loggingService, 'tests', of(config.shutdownTimeout)); + server = new HttpServer(coreContext, 'tests', of(config.shutdownTimeout)); }); describe('Graceful shutdown', () => { diff --git a/src/core/server/integration_tests/http/tls_config_reload.test.ts b/src/core/server/integration_tests/http/tls_config_reload.test.ts index 3603f76ebc670..ad2c530faae52 100644 --- a/src/core/server/integration_tests/http/tls_config_reload.test.ts +++ b/src/core/server/integration_tests/http/tls_config_reload.test.ts @@ -10,7 +10,6 @@ import supertest from 'supertest'; import { duration } from 'moment'; import { BehaviorSubject, of } from 'rxjs'; import { KBN_CERT_PATH, KBN_KEY_PATH, ES_KEY_PATH, ES_CERT_PATH } from '@kbn/dev-utils'; -import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; import { Router } from '@kbn/core-http-router-server-internal'; import { HttpServer, @@ -20,6 +19,8 @@ import { externalUrlConfig, } from '@kbn/core-http-server-internal'; import { isServerTLS, flattenCertificateChain, fetchPeerCertificate } from './tls_utils'; +import { mockCoreContext } from '@kbn/core-base-server-mocks'; +import type { Logger } from '@kbn/logging'; const CSP_CONFIG = cspConfig.schema.validate({}); const EXTERNAL_URL_CONFIG = externalUrlConfig.schema.validate({}); @@ -27,16 +28,16 @@ const enhanceWithContext = (fn: (...args: any[]) => any) => fn.bind(null, {}); describe('HttpServer - TLS config', () => { let server: HttpServer; - let logger: ReturnType; + let logger: Logger; beforeAll(() => { process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; }); beforeEach(() => { - const loggingService = loggingSystemMock.create(); - logger = loggingSystemMock.createLogger(); - server = new HttpServer(loggingService, 'tests', of(duration('1s'))); + const coreContext = mockCoreContext.create(); + logger = coreContext.logger.get(); + server = new HttpServer(coreContext, 'tests', of(duration('1s'))); }); it('supports dynamic reloading of the TLS configuration', async () => { diff --git a/src/core/server/integration_tests/status/routes/status.test.ts b/src/core/server/integration_tests/status/routes/status.test.ts index 0d7d6a84e2497..755b9fc9b46f6 100644 --- a/src/core/server/integration_tests/status/routes/status.test.ts +++ b/src/core/server/integration_tests/status/routes/status.test.ts @@ -83,6 +83,7 @@ describe('GET /api/status', () => { branch: 'xbranch', buildNum: 1234, buildSha: 'xsha', + buildShaShort: 'x', dist: true, version: '9.9.9-SNAPSHOT', buildDate: new Date('2023-05-15T23:12:09.000Z'), diff --git a/src/core/server/mocks.ts b/src/core/server/mocks.ts index 62dd66f63ec62..b76a9c37dd6da 100644 --- a/src/core/server/mocks.ts +++ b/src/core/server/mocks.ts @@ -98,6 +98,7 @@ function pluginInitializerContextMock(config: T = {} as T) { branch: 'branch', buildNum: 100, buildSha: 'buildSha', + buildShaShort: 'buildShaShort', dist: false, buildDate: new Date('2023-05-15T23:12:09.000Z'), buildFlavor: 'traditional', diff --git a/src/core/tsconfig.json b/src/core/tsconfig.json index 78219114a51df..06e6cf68d3a94 100644 --- a/src/core/tsconfig.json +++ b/src/core/tsconfig.json @@ -157,6 +157,7 @@ "@kbn/core-plugins-contracts-server", "@kbn/dev-utils", "@kbn/server-http-tools", + "@kbn/core-base-server-mocks", ], "exclude": [ "target/**/*", diff --git a/src/plugins/charts/common/constants.ts b/src/plugins/charts/common/constants.ts index 6d895dcba26ed..050157dd6cec7 100644 --- a/src/plugins/charts/common/constants.ts +++ b/src/plugins/charts/common/constants.ts @@ -13,7 +13,7 @@ export const paletteIds = [ 'custom', 'status', 'temperature', - 'complimentary', + 'complementary', 'negative', 'positive', 'cool', diff --git a/src/plugins/charts/public/services/palettes/palettes.tsx b/src/plugins/charts/public/services/palettes/palettes.tsx index 6cbbb548c7fed..4df7e2bea16a9 100644 --- a/src/plugins/charts/public/services/palettes/palettes.tsx +++ b/src/plugins/charts/public/services/palettes/palettes.tsx @@ -273,11 +273,11 @@ export const buildPalettes: ( title: i18n.translate('charts.palettes.temperatureLabel', { defaultMessage: 'Temperature' }), ...buildGradient('temperature', euiPaletteForTemperature), }, - complimentary: { - title: i18n.translate('charts.palettes.complimentaryLabel', { - defaultMessage: 'Complimentary', + complementary: { + title: i18n.translate('charts.palettes.complementaryLabel', { + defaultMessage: 'Complementary', }), - ...buildGradient('complimentary', euiPaletteComplementary), + ...buildGradient('complementary', euiPaletteComplementary), }, negative: { title: i18n.translate('charts.palettes.negativeLabel', { defaultMessage: 'Negative' }), diff --git a/src/plugins/charts/public/services/palettes/service.ts b/src/plugins/charts/public/services/palettes/service.ts index 0608c897f6306..7afa85ab58174 100644 --- a/src/plugins/charts/public/services/palettes/service.ts +++ b/src/plugins/charts/public/services/palettes/service.ts @@ -7,6 +7,7 @@ */ import type { PaletteRegistry, PaletteDefinition } from '@kbn/coloring'; +import { getActivePaletteName } from '@kbn/coloring'; import type { ExpressionsSetup } from '@kbn/expressions-plugin/public'; import type { ChartsPluginSetup } from '../..'; import type { LegacyColorsService } from '../legacy_colors'; @@ -29,7 +30,8 @@ export class PaletteService { } return { get: (name: string) => { - return this.palettes![name]; + const paletteName = getActivePaletteName(name); + return this.palettes![paletteName]; }, getAll: () => { return Object.values(this.palettes!); diff --git a/src/plugins/dashboard/public/dashboard_app/top_nav/add_panel_action_menu_items.test.ts b/src/plugins/dashboard/public/dashboard_app/top_nav/add_panel_action_menu_items.test.ts index 024a7518aba0c..aa8474a0879cc 100644 --- a/src/plugins/dashboard/public/dashboard_app/top_nav/add_panel_action_menu_items.test.ts +++ b/src/plugins/dashboard/public/dashboard_app/top_nav/add_panel_action_menu_items.test.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import { getMockPresentationContainer } from '@kbn/presentation-containers/mocks'; import { getAddPanelActionMenuItems } from './add_panel_action_menu_items'; describe('getAddPanelActionMenuItems', () => { @@ -21,7 +22,11 @@ describe('getAddPanelActionMenuItems', () => { execute: jest.fn(), }, ]; - const items = getAddPanelActionMenuItems(registeredActions, jest.fn(), jest.fn(), jest.fn()); + const items = getAddPanelActionMenuItems( + getMockPresentationContainer(), + registeredActions, + jest.fn() + ); expect(items).toStrictEqual([ { 'data-test-subj': 'create-action-Action name', @@ -34,7 +39,7 @@ describe('getAddPanelActionMenuItems', () => { }); it('returns empty array if no actions have been registered', async () => { - const items = getAddPanelActionMenuItems([], jest.fn(), jest.fn(), jest.fn()); + const items = getAddPanelActionMenuItems(getMockPresentationContainer(), [], jest.fn()); expect(items).toStrictEqual([]); }); }); diff --git a/src/plugins/dashboard/public/dashboard_app/top_nav/add_panel_action_menu_items.ts b/src/plugins/dashboard/public/dashboard_app/top_nav/add_panel_action_menu_items.ts index 3df8343887982..a6435579ff286 100644 --- a/src/plugins/dashboard/public/dashboard_app/top_nav/add_panel_action_menu_items.ts +++ b/src/plugins/dashboard/public/dashboard_app/top_nav/add_panel_action_menu_items.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ import type { ActionExecutionContext, Action } from '@kbn/ui-actions-plugin/public'; -import type { EmbeddableFactory } from '@kbn/embeddable-plugin/public'; +import { PresentationContainer } from '@kbn/presentation-containers'; import { addPanelMenuTrigger } from '../../triggers'; const onAddPanelActionClick = @@ -27,16 +27,14 @@ const onAddPanelActionClick = }; export const getAddPanelActionMenuItems = ( + api: PresentationContainer, actions: Array> | undefined, - createNewEmbeddable: (embeddableFactory: EmbeddableFactory) => void, - deleteEmbeddable: (embeddableId: string) => void, closePopover: () => void ) => { return ( actions?.map((item) => { const context = { - createNewEmbeddable, - deleteEmbeddable, + embeddable: api, trigger: addPanelMenuTrigger, }; const actionName = item.getDisplayName(context); diff --git a/src/plugins/dashboard/public/dashboard_app/top_nav/dashboard_editing_toolbar.tsx b/src/plugins/dashboard/public/dashboard_app/top_nav/dashboard_editing_toolbar.tsx index e39e227da0643..579d6d17d3a94 100644 --- a/src/plugins/dashboard/public/dashboard_app/top_nav/dashboard_editing_toolbar.tsx +++ b/src/plugins/dashboard/public/dashboard_app/top_nav/dashboard_editing_toolbar.tsx @@ -11,9 +11,7 @@ import { METRIC_TYPE } from '@kbn/analytics'; import { useEuiTheme } from '@elastic/eui'; import { AddFromLibraryButton, Toolbar, ToolbarButton } from '@kbn/shared-ux-button-toolbar'; -import { EmbeddableFactory, EmbeddableInput } from '@kbn/embeddable-plugin/public'; import { BaseVisType, VisTypeAlias } from '@kbn/visualizations-plugin/public'; -import { isExplicitInputWithAttributes } from '@kbn/embeddable-plugin/public'; import { getCreateVisualizationButtonTitle } from '../_dashboard_app_strings'; import { EditorMenu } from './editor_menu'; @@ -21,13 +19,11 @@ import { useDashboardAPI } from '../dashboard_app'; import { pluginServices } from '../../services/plugin_services'; import { ControlsToolbarButton } from './controls_toolbar_button'; import { DASHBOARD_UI_METRIC_ID } from '../../dashboard_constants'; -import { dashboardReplacePanelActionStrings } from '../../dashboard_actions/_dashboard_actions_strings'; export function DashboardEditingToolbar({ isDisabled }: { isDisabled?: boolean }) { const { usageCollection, data: { search }, - notifications: { toasts }, embeddable: { getStateTransfer }, visualizations: { getAliases: getVisTypeAliases }, } = pluginServices.getServices(); @@ -85,74 +81,9 @@ export function DashboardEditingToolbar({ isDisabled }: { isDisabled?: boolean } * initialInput: Optional, use it in case you want to pass your own input to the factory * dismissNotification: Optional, if not passed a toast will appear in the dashboard */ - const createNewEmbeddable = useCallback( - async ( - embeddableFactory: EmbeddableFactory, - initialInput?: Partial, - dismissNotification?: boolean - ) => { - if (trackUiMetric) { - trackUiMetric(METRIC_TYPE.CLICK, embeddableFactory.type); - } - - let explicitInput: Partial; - let attributes: unknown; - try { - if (initialInput) { - explicitInput = initialInput; - } else { - const explicitInputReturn = await embeddableFactory.getExplicitInput( - undefined, - dashboard - ); - if (isExplicitInputWithAttributes(explicitInputReturn)) { - explicitInput = explicitInputReturn.newInput; - attributes = explicitInputReturn.attributes; - } else { - explicitInput = explicitInputReturn; - } - } - } catch (e) { - // error likely means user canceled embeddable creation - return; - } - - const newEmbeddable = await dashboard.addNewEmbeddable( - embeddableFactory.type, - explicitInput, - attributes - ); - - if (newEmbeddable) { - dashboard.setScrollToPanelId(newEmbeddable.id); - dashboard.setHighlightPanelId(newEmbeddable.id); - - if (!dismissNotification) { - toasts.addSuccess({ - title: dashboardReplacePanelActionStrings.getSuccessMessage(newEmbeddable.getTitle()), - 'data-test-subj': 'addEmbeddableToDashboardSuccess', - }); - } - } - return newEmbeddable; - }, - [trackUiMetric, dashboard, toasts] - ); - - const deleteEmbeddable = useCallback( - (embeddableId: string) => { - dashboard.removeEmbeddable(embeddableId); - }, - [dashboard] - ); const extraButtons = [ - , + , dashboard.addFromLibrary()} size="s" diff --git a/src/plugins/dashboard/public/dashboard_app/top_nav/editor_menu.tsx b/src/plugins/dashboard/public/dashboard_app/top_nav/editor_menu.tsx index 050b3ed818877..7df2dec31ae90 100644 --- a/src/plugins/dashboard/public/dashboard_app/top_nav/editor_menu.tsx +++ b/src/plugins/dashboard/public/dashboard_app/top_nav/editor_menu.tsx @@ -17,25 +17,15 @@ import { useEuiTheme, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { ToolbarPopover } from '@kbn/shared-ux-button-toolbar'; import type { Action } from '@kbn/ui-actions-plugin/public'; +import { ToolbarPopover } from '@kbn/shared-ux-button-toolbar'; +import { PresentationContainer } from '@kbn/presentation-containers'; import { type BaseVisType, VisGroups, type VisTypeAlias } from '@kbn/visualizations-plugin/public'; import type { EmbeddableFactory } from '@kbn/embeddable-plugin/public'; import { pluginServices } from '../../services/plugin_services'; import { DASHBOARD_APP_ID } from '../../dashboard_constants'; import { ADD_PANEL_TRIGGER } from '../../triggers'; import { getAddPanelActionMenuItems } from './add_panel_action_menu_items'; - -interface Props { - isDisabled?: boolean; - /** Handler for creating new visualization of a specified type */ - createNewVisType: (visType: BaseVisType | VisTypeAlias) => () => void; - /** Handler for creating a new embeddable of a specified type */ - createNewEmbeddable: (embeddableFactory: EmbeddableFactory) => void; - /** Handler for deleting an embeddable */ - deleteEmbeddable: (embeddableId: string) => void; -} - interface FactoryGroup { id: string; appName: string; @@ -51,10 +41,14 @@ interface UnwrappedEmbeddableFactory { export const EditorMenu = ({ createNewVisType, - createNewEmbeddable, - deleteEmbeddable, isDisabled, -}: Props) => { + api, +}: { + api: PresentationContainer; + isDisabled?: boolean; + /** Handler for creating new visualization of a specified type */ + createNewVisType: (visType: BaseVisType | VisTypeAlias) => () => void; +}) => { const isMounted = useRef(false); const { embeddable, @@ -148,16 +142,15 @@ export const EditorMenu = ({ // Retrieve ADD_PANEL_TRIGGER actions useEffect(() => { async function loadPanelActions() { - const registeredActions = await uiActions?.getTriggerCompatibleActions?.( - ADD_PANEL_TRIGGER, - {} - ); + const registeredActions = await uiActions?.getTriggerCompatibleActions?.(ADD_PANEL_TRIGGER, { + embeddable: api, + }); if (isMounted.current) { setAddPanelActions(registeredActions); } } loadPanelActions(); - }, [uiActions]); + }, [uiActions, api]); factories.forEach(({ factory }) => { const { grouping } = factory; @@ -249,7 +242,7 @@ export const EditorMenu = ({ toolTipContent, onClick: async () => { closePopover(); - createNewEmbeddable(factory); + api.addNewPanel({ panelType: factory.type }, true); }, 'data-test-subj': `createNew-${factory.type}`, }; @@ -274,12 +267,7 @@ export const EditorMenu = ({ })), ...promotedVisTypes.map(getVisTypeMenuItem), - ...getAddPanelActionMenuItems( - addPanelActions, - createNewEmbeddable, - deleteEmbeddable, - closePopover - ), + ...getAddPanelActionMenuItems(api, addPanelActions, closePopover), ]; if (aggsBasedVisTypes.length > 0) { initialPanelItems.push({ diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/api/run_save_functions.tsx b/src/plugins/dashboard/public/dashboard_container/embeddable/api/run_save_functions.tsx index 66c52f14a0002..75fd959c8e571 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/api/run_save_functions.tsx +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/api/run_save_functions.tsx @@ -14,7 +14,9 @@ import { showSaveModal } from '@kbn/saved-objects-plugin/public'; import { cloneDeep } from 'lodash'; import React from 'react'; import { batch } from 'react-redux'; -import { DashboardContainerInput } from '../../../../common'; + +import { EmbeddableInput, isReferenceOrValueEmbeddable } from '@kbn/embeddable-plugin/public'; +import { DashboardContainerInput, DashboardPanelMap } from '../../../../common'; import { DASHBOARD_CONTENT_ID, SAVED_OBJECT_POST_TIME } from '../../../dashboard_constants'; import { SaveDashboardReturn, @@ -185,7 +187,8 @@ export async function runQuickSave(this: DashboardContainer) { if (managed) return; const nextPanels = await serializeAllPanelState(this); - let stateToSave: SavedDashboardInput = { ...currentState, panels: nextPanels }; + const dashboardStateToSave: DashboardContainerInput = { ...currentState, panels: nextPanels }; + let stateToSave: SavedDashboardInput = dashboardStateToSave; let persistableControlGroupInput: PersistableControlGroupInput | undefined; if (this.controlGroup) { persistableControlGroupInput = this.controlGroup.getPersistableInput(); @@ -198,7 +201,7 @@ export async function runQuickSave(this: DashboardContainer) { saveOptions: {}, }); - this.dispatch.setLastSavedInput(currentState); + this.dispatch.setLastSavedInput(dashboardStateToSave); this.lastSavedState.next(); if (this.controlGroup && persistableControlGroupInput) { this.controlGroup.dispatch.setLastSavedInput(persistableControlGroupInput); @@ -241,12 +244,38 @@ export async function runClone(this: DashboardContainer) { }; } + const isManaged = this.getState().componentState.managed; + const newPanels = await (async () => { + if (!isManaged) return currentState.panels; + + // this is a managed dashboard - unlink all by reference embeddables on clone + const unlinkedPanels: DashboardPanelMap = {}; + for (const [panelId, panel] of Object.entries(currentState.panels)) { + const child = this.getChild(panelId); + if ( + child && + isReferenceOrValueEmbeddable(child) && + child.inputIsRefType(child.getInput() as EmbeddableInput) + ) { + const valueTypeInput = await child.getInputAsValueType(); + unlinkedPanels[panelId] = { + ...panel, + explicitInput: valueTypeInput, + }; + continue; + } + unlinkedPanels[panelId] = panel; + } + return unlinkedPanels; + })(); + const saveResult = await saveDashboardState({ saveOptions: { saveAsCopy: true, }, currentState: { ...stateToSave, + panels: newPanels, title: newTitle, }, }); diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx b/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx index 600ee15da2221..e58e8597500dd 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import { v4 } from 'uuid'; import { omit } from 'lodash'; import React, { createContext, useContext } from 'react'; import ReactDOM from 'react-dom'; @@ -20,6 +21,8 @@ import type { DataView } from '@kbn/data-views-plugin/public'; import { reportPerformanceMetricEvent } from '@kbn/ebt-tools'; import { Container, + EmbeddableFactoryNotFoundError, + isExplicitInputWithAttributes, DefaultEmbeddableApi, PanelNotFoundError, ReactEmbeddableParentContext, @@ -30,8 +33,9 @@ import { type EmbeddableOutput, type IEmbeddable, } from '@kbn/embeddable-plugin/public'; -import type { Filter, Query, TimeRange } from '@kbn/es-query'; +import { METRIC_TYPE } from '@kbn/analytics'; import { I18nProvider } from '@kbn/i18n-react'; +import type { Filter, Query, TimeRange } from '@kbn/es-query'; import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; import { PanelPackage } from '@kbn/presentation-containers'; import { ReduxEmbeddableTools, ReduxToolsPackage } from '@kbn/presentation-util-plugin/public'; @@ -40,7 +44,13 @@ import { ExitFullScreenButtonKibanaProvider } from '@kbn/shared-ux-button-exit-f import { DashboardLocatorParams, DASHBOARD_CONTAINER_TYPE } from '../..'; import { DashboardContainerInput, DashboardPanelState } from '../../../common'; -import { DASHBOARD_APP_ID, DASHBOARD_LOADED_EVENT } from '../../dashboard_constants'; +import { + DASHBOARD_APP_ID, + DASHBOARD_LOADED_EVENT, + DASHBOARD_UI_METRIC_ID, + DEFAULT_PANEL_HEIGHT, + DEFAULT_PANEL_WIDTH, +} from '../../dashboard_constants'; import { DashboardAnalyticsService } from '../../services/analytics/types'; import { DashboardCapabilitiesService } from '../../services/dashboard_capabilities/types'; import { pluginServices } from '../../services/plugin_services'; @@ -70,6 +80,8 @@ import { dashboardTypeDisplayLowercase, dashboardTypeDisplayName, } from './dashboard_container_factory'; +import { dashboardReplacePanelActionStrings } from '../../dashboard_actions/_dashboard_actions_strings'; +import { panelPlacementStrategies } from '../component/panel_placement/place_new_panel_strategies'; export interface InheritedChildInput { filters: Filter[]; @@ -142,6 +154,9 @@ export class DashboardContainer private chrome; private customBranding; + private trackPanelAddMetric: + | ((type: string, eventNames: string | string[], count?: number | undefined) => void) + | undefined; // new embeddable framework public reactEmbeddableChildren: BehaviorSubject<{ [key: string]: DefaultEmbeddableApi }> = new BehaviorSubject<{ [key: string]: DefaultEmbeddableApi }>({}); @@ -156,9 +171,9 @@ export class DashboardContainer initialComponentState?: DashboardPublicState ) { const { + usageCollection, embeddable: { getEmbeddableFactory }, } = pluginServices.getServices(); - super( { ...initialInput, @@ -168,6 +183,11 @@ export class DashboardContainer parent ); + this.trackPanelAddMetric = usageCollection.reportUiCounter?.bind( + usageCollection, + DASHBOARD_UI_METRIC_ID + ); + ({ analytics: this.analyticsService, settings: { @@ -396,6 +416,88 @@ export class DashboardContainer return newId; } + public async addNewPanel( + panelPackage: PanelPackage, + displaySuccessMessage?: boolean + ) { + const { + notifications: { toasts }, + embeddable: { getEmbeddableFactory }, + } = pluginServices.getServices(); + + const onSuccess = (id?: string, title?: string) => { + if (!displaySuccessMessage) return; + toasts.addSuccess({ + title: dashboardReplacePanelActionStrings.getSuccessMessage(title), + 'data-test-subj': 'addEmbeddableToDashboardSuccess', + }); + this.setScrollToPanelId(id); + this.setHighlightPanelId(id); + }; + + if (this.trackPanelAddMetric) { + this.trackPanelAddMetric(METRIC_TYPE.CLICK, panelPackage.panelType); + } + if (reactEmbeddableRegistryHasKey(panelPackage.panelType)) { + const newId = v4(); + const { newPanelPlacement, otherPanels } = panelPlacementStrategies.findTopLeftMostOpenSpace({ + currentPanels: this.getInput().panels, + height: DEFAULT_PANEL_HEIGHT, + width: DEFAULT_PANEL_WIDTH, + }); + const newPanel: DashboardPanelState = { + type: panelPackage.panelType, + gridData: { + ...newPanelPlacement, + i: newId, + }, + explicitInput: { + ...panelPackage.initialState, + id: newId, + }, + }; + this.updateInput({ panels: { ...otherPanels, [newId]: newPanel } }); + onSuccess(newId, newPanel.explicitInput.title); + return; + } + + const embeddableFactory = getEmbeddableFactory(panelPackage.panelType); + if (!embeddableFactory) { + throw new EmbeddableFactoryNotFoundError(panelPackage.panelType); + } + const initialInput = panelPackage.initialState as Partial; + + let explicitInput: Partial; + let attributes: unknown; + try { + if (initialInput) { + explicitInput = initialInput; + } else { + const explicitInputReturn = await embeddableFactory.getExplicitInput(undefined, this); + if (isExplicitInputWithAttributes(explicitInputReturn)) { + explicitInput = explicitInputReturn.newInput; + attributes = explicitInputReturn.attributes; + } else { + explicitInput = explicitInputReturn; + } + } + } catch (e) { + // error likely means user canceled embeddable creation + return; + } + + const newEmbeddable = await this.addNewEmbeddable( + embeddableFactory.type, + explicitInput, + attributes + ); + + if (newEmbeddable) { + onSuccess(newEmbeddable.id, newEmbeddable.getTitle()); + } + return newEmbeddable as ApiType; + } + public getDashboardPanelFromId = async (panelId: string) => { const panel = this.getInput().panels[panelId]; if (reactEmbeddableRegistryHasKey(panel.type)) { diff --git a/src/plugins/data/common/search/expressions/esql.ts b/src/plugins/data/common/search/expressions/esql.ts index 3275a296568fd..ce32d3626bcfc 100644 --- a/src/plugins/data/common/search/expressions/esql.ts +++ b/src/plugins/data/common/search/expressions/esql.ts @@ -11,7 +11,6 @@ import { castEsToKbnFieldTypeName, ES_FIELD_TYPES, KBN_FIELD_TYPES } from '@kbn/ import { i18n } from '@kbn/i18n'; import type { Datatable, - DatatableColumn, DatatableColumnType, ExpressionFunctionDefinition, } from '@kbn/expressions-plugin/common'; @@ -195,7 +194,10 @@ export const getEsqlFn = ({ getStartDependencies }: EsqlFnArguments) => { return search< IKibanaSearchRequest, IKibanaSearchResponse - >({ params }, { abortSignal, strategy: ESQL_ASYNC_SEARCH_STRATEGY }).pipe( + >( + { params: { ...params, dropNullColumns: true } }, + { abortSignal, strategy: ESQL_ASYNC_SEARCH_STRATEGY } + ).pipe( catchError((error) => { if (!error.attributes) { error.message = `Unexpected error from Elasticsearch: ${error.message}`; @@ -238,37 +240,28 @@ export const getEsqlFn = ({ getStartDependencies }: EsqlFnArguments) => { ); }), map(({ rawResponse: body, warning }) => { - const columns = - body.columns?.map(({ name, type }) => ({ - id: name, - name, - meta: { type: normalizeType(type) }, - })) ?? []; // all_columns in the response means that there is a separation between // columns with data and empty columns // columns contain only columns with data while all_columns everything const hasEmptyColumns = body.all_columns && body.all_columns?.length > body.columns.length; + const lookup = new Set( + hasEmptyColumns ? body.columns?.map(({ name }) => name) || [] : [] + ); + const allColumns = + (body.all_columns ?? body.columns)?.map(({ name, type }) => ({ + id: name, + name, + meta: { type: normalizeType(type) }, + isNull: hasEmptyColumns ? !lookup.has(name) : false, + })) ?? []; - let emptyColumns: DatatableColumn[] = []; - + // sort only in case of empty columns to correctly align columns to items in values array if (hasEmptyColumns) { - const difference = - body.all_columns?.filter((col1) => { - return !body.columns.some((col2) => { - return col1.name === col2.name; - }); - }) ?? []; - emptyColumns = - difference?.map(({ name, type }) => ({ - id: name, - name, - meta: { type: normalizeType(type) }, - isNull: true, - })) ?? []; + allColumns.sort((a, b) => Number(a.isNull) - Number(b.isNull)); } - const allColumns = [...columns, ...emptyColumns]; - const columnNames = allColumns.map(({ name }) => name); + const columnNames = allColumns?.map(({ name }) => name); + const rows = body.values.map((row) => zipObject(columnNames, row)); return { diff --git a/src/plugins/data/server/search/strategies/ese_search/ese_search_strategy.ts b/src/plugins/data/server/search/strategies/ese_search/ese_search_strategy.ts index 174f9924f1cc7..bcf8a3fafcf16 100644 --- a/src/plugins/data/server/search/strategies/ese_search/ese_search_strategy.ts +++ b/src/plugins/data/server/search/strategies/ese_search/ese_search_strategy.ts @@ -13,7 +13,7 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { firstValueFrom, from } from 'rxjs'; import { getKbnServerError } from '@kbn/kibana-utils-plugin/server'; import { IAsyncSearchRequestParams } from '../..'; -import { getKbnSearchError, KbnSearchError } from '../../report_search_error'; +import { getKbnSearchError } from '../../report_search_error'; import type { ISearchStrategy, SearchStrategyDependencies } from '../../types'; import type { IAsyncSearchOptions, @@ -170,14 +170,11 @@ export const enhancedEsSearchStrategyProvider = ( */ search: (request, options: IAsyncSearchOptions, deps) => { logger.debug(`search ${JSON.stringify(request.params) || request.id}`); - if (request.indexType && request.indexType !== 'rollup') { - throw new KbnSearchError('Unknown indexType', 400); - } - if (request.indexType === undefined || !deps.rollupsEnabled) { - return asyncSearch(request, options, deps); - } else { + if (request.indexType === 'rollup' && deps.rollupsEnabled) { return from(rollupSearch(request, options, deps)); + } else { + return asyncSearch(request, options, deps); } }, /** diff --git a/src/plugins/data/server/search/strategies/esql_async_search/esql_async_search_strategy.test.ts b/src/plugins/data/server/search/strategies/esql_async_search/esql_async_search_strategy.test.ts index b8a16113d1280..bc7bf31224940 100644 --- a/src/plugins/data/server/search/strategies/esql_async_search/esql_async_search_strategy.test.ts +++ b/src/plugins/data/server/search/strategies/esql_async_search/esql_async_search_strategy.test.ts @@ -123,7 +123,7 @@ describe('ES|QL async search strategy', () => { it('sets transport options on POST requests', async () => { const transportOptions = { maxRetries: 1 }; mockApiCaller.mockResolvedValueOnce(mockAsyncResponse); - const params = {}; + const params = { query: 'from logs' }; const esSearch = esqlAsyncSearchStrategyProvider(mockSearchConfig, mockLogger); await firstValueFrom( @@ -139,6 +139,7 @@ describe('ES|QL async search strategy', () => { keep_alive: '60000ms', wait_for_completion_timeout: '100ms', keep_on_completion: false, + query: 'from logs', }, }), expect.objectContaining({ maxRetries: 1, meta: true, signal: undefined }) diff --git a/src/plugins/data/server/search/strategies/esql_async_search/esql_async_search_strategy.ts b/src/plugins/data/server/search/strategies/esql_async_search/esql_async_search_strategy.ts index f052e6749e726..8bde400db2b45 100644 --- a/src/plugins/data/server/search/strategies/esql_async_search/esql_async_search_strategy.ts +++ b/src/plugins/data/server/search/strategies/esql_async_search/esql_async_search_strategy.ts @@ -11,6 +11,7 @@ import { catchError, tap } from 'rxjs/operators'; import { getKbnServerError } from '@kbn/kibana-utils-plugin/server'; import { SqlQueryRequest } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { SqlGetAsyncResponse } from '@elastic/elasticsearch/lib/api/types'; +import type { ESQLSearchParams } from '@kbn/es-types'; import { getCommonDefaultAsyncSubmitParams, getCommonDefaultAsyncGetParams, @@ -22,12 +23,18 @@ import { IKibanaSearchRequest, IKibanaSearchResponse, pollSearch } from '../../. import { toAsyncKibanaSearchResponse } from './response_utils'; import { SearchConfigSchema } from '../../../../config'; +// `drop_null_columns` is going to change the response +// now we get `all_columns` and `columns` +// `columns` contain only columns with data +// `all_columns` contain everything +type ESQLQueryRequest = ESQLSearchParams & SqlQueryRequest['body']; + export const esqlAsyncSearchStrategyProvider = ( searchConfig: SearchConfigSchema, logger: Logger, useInternalUser: boolean = false ): ISearchStrategy< - IKibanaSearchRequest, + IKibanaSearchRequest, IKibanaSearchResponse > => { function cancelAsyncSearch(id: string, esClient: IScopedClusterClient) { @@ -46,12 +53,12 @@ export const esqlAsyncSearchStrategyProvider = ( } function asyncSearch( - { id, ...request }: IKibanaSearchRequest, + { id, ...request }: IKibanaSearchRequest, options: IAsyncSearchOptions, { esClient, uiSettingsClient }: SearchStrategyDependencies ) { const client = useInternalUser ? esClient.asInternalUser : esClient.asCurrentUser; - + const { dropNullColumns, ...requestParams } = request.params ?? {}; const search = async () => { const params = id ? { @@ -63,7 +70,7 @@ export const esqlAsyncSearchStrategyProvider = ( } : { ...(await getCommonDefaultAsyncSubmitParams(searchConfig, options)), - ...request.params, + ...requestParams, }; const { body, headers, meta } = id ? await client.transport.request( @@ -79,7 +86,7 @@ export const esqlAsyncSearchStrategyProvider = ( method: 'POST', path: `/_query/async`, body: params, - querystring: 'drop_null_columns', + querystring: dropNullColumns ? 'drop_null_columns' : '', }, { ...options.transport, signal: options.abortSignal, meta: true } ); diff --git a/src/plugins/data/server/search/strategies/esql_search/esql_search_strategy.ts b/src/plugins/data/server/search/strategies/esql_search/esql_search_strategy.ts index e9a6499b4aa1b..73e77e53555e2 100644 --- a/src/plugins/data/server/search/strategies/esql_search/esql_search_strategy.ts +++ b/src/plugins/data/server/search/strategies/esql_search/esql_search_strategy.ts @@ -32,12 +32,16 @@ export const esqlSearchStrategyProvider = ( const search = async () => { try { - const { terminateAfter, ...requestParams } = request.params ?? {}; + // `drop_null_columns` is going to change the response + // now we get `all_columns` and `columns` + // `columns` contain only columns with data + // `all_columns` contain everything + const { terminateAfter, dropNullColumns, ...requestParams } = request.params ?? {}; const { headers, body, meta } = await esClient.asCurrentUser.transport.request( { method: 'POST', path: `/_query`, - querystring: 'drop_null_columns', + querystring: dropNullColumns ? 'drop_null_columns' : '', body: { ...requestParams, }, diff --git a/src/plugins/data_views/common/data_views/__snapshots__/data_views.test.ts.snap b/src/plugins/data_views/common/data_views/__snapshots__/data_views.test.ts.snap index 53b77e28a416c..cb9bb0517f507 100644 --- a/src/plugins/data_views/common/data_views/__snapshots__/data_views.test.ts.snap +++ b/src/plugins/data_views/common/data_views/__snapshots__/data_views.test.ts.snap @@ -7,6 +7,7 @@ FldList [ "conflictDescriptions": undefined, "count": 5, "customLabel": "A Runtime Field", + "defaultFormatter": undefined, "esTypes": Array [ "keyword", ], diff --git a/src/plugins/data_views/common/data_views/abstract_data_views.ts b/src/plugins/data_views/common/data_views/abstract_data_views.ts index 15ec8342cfd5b..cc69ed7e7cedf 100644 --- a/src/plugins/data_views/common/data_views/abstract_data_views.ts +++ b/src/plugins/data_views/common/data_views/abstract_data_views.ts @@ -24,6 +24,7 @@ import type { RuntimeField, } from '../types'; import { removeFieldAttrs } from './utils'; +import { metaUnitsToFormatter } from './meta_units_to_formatter'; import type { DataViewAttributes, FieldAttrs, FieldAttrSet } from '..'; @@ -251,6 +252,11 @@ export abstract class AbstractDataView { return fieldFormat; } + const fmt = field.defaultFormatter ? metaUnitsToFormatter[field.defaultFormatter] : undefined; + if (fmt) { + return this.fieldFormats.getInstance(fmt.id, fmt.params); + } + return this.fieldFormats.getDefaultInstance( field.type as KBN_FIELD_TYPES, field.esTypes as ES_FIELD_TYPES[] diff --git a/src/plugins/data_views/common/data_views/meta_units_to_formatter.ts b/src/plugins/data_views/common/data_views/meta_units_to_formatter.ts new file mode 100644 index 0000000000000..2989bf5828c37 --- /dev/null +++ b/src/plugins/data_views/common/data_views/meta_units_to_formatter.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 { FieldFormatParams } from '@kbn/field-formats-plugin/common'; + +const timeUnitToDurationFmt = (inputFormat = 'milliseconds') => { + return { + id: 'duration', + params: { + inputFormat, + outputFormat: 'humanizePrecise', + outputPrecision: 2, + includeSpaceWithSuffix: true, + useShortSuffix: true, + }, + }; +}; + +export const metaUnitsToFormatter: Record = { + percent: { id: 'percent' }, + byte: { id: 'bytes' }, + nanos: timeUnitToDurationFmt('nanoseconds'), + micros: timeUnitToDurationFmt('microseconds'), + ms: timeUnitToDurationFmt('milliseconds'), + s: timeUnitToDurationFmt('seconds'), + m: timeUnitToDurationFmt('minutes'), + h: timeUnitToDurationFmt('hours'), + d: timeUnitToDurationFmt('days'), +}; diff --git a/src/plugins/data_views/common/fields/data_view_field.ts b/src/plugins/data_views/common/fields/data_view_field.ts index 36cd78682aa97..52c304dbf27f1 100644 --- a/src/plugins/data_views/common/fields/data_view_field.ts +++ b/src/plugins/data_views/common/fields/data_view_field.ts @@ -69,6 +69,10 @@ export class DataViewField implements DataViewFieldBase { this.spec.count = count; } + public get defaultFormatter() { + return this.spec.defaultFormatter; + } + /** * Returns runtime field definition or undefined if field is not runtime field. */ @@ -370,6 +374,7 @@ export class DataViewField implements DataViewFieldBase { readFromDocValues: this.readFromDocValues, subType: this.subType, customLabel: this.customLabel, + defaultFormatter: this.defaultFormatter, }; } @@ -403,6 +408,7 @@ export class DataViewField implements DataViewFieldBase { timeSeriesMetric: this.spec.timeSeriesMetric, timeZone: this.spec.timeZone, fixedInterval: this.spec.fixedInterval, + defaultFormatter: this.defaultFormatter, }; // Filter undefined values from the spec diff --git a/src/plugins/data_views/common/types.ts b/src/plugins/data_views/common/types.ts index 2177f51621fec..caf1fc80dc5b6 100644 --- a/src/plugins/data_views/common/types.ts +++ b/src/plugins/data_views/common/types.ts @@ -462,6 +462,8 @@ export type FieldSpec = DataViewFieldBase & { * Name of parent field for composite runtime field subfields. */ parentName?: string; + + defaultFormatter?: string; }; export type DataViewFieldMap = Record; 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 aca312c8ea64d..15ee00e5118cb 100644 --- a/src/plugins/data_views/server/fetcher/index_patterns_fetcher.ts +++ b/src/plugins/data_views/server/fetcher/index_patterns_fetcher.ts @@ -32,6 +32,7 @@ export interface FieldDescriptor { timeZone?: string[]; timeSeriesMetric?: estypes.MappingTimeSeriesMetricType; timeSeriesDimension?: boolean; + defaultFormatter?: string; } interface FieldSubType { diff --git a/src/plugins/data_views/server/fetcher/lib/field_capabilities/field_caps_response.test.js b/src/plugins/data_views/server/fetcher/lib/field_capabilities/field_caps_response.test.js index e0722125cf7d6..61f345876ac8d 100644 --- a/src/plugins/data_views/server/fetcher/lib/field_capabilities/field_caps_response.test.js +++ b/src/plugins/data_views/server/fetcher/lib/field_capabilities/field_caps_response.test.js @@ -165,5 +165,22 @@ describe('index_patterns/field_capabilities/field_caps_response', () => { expect(child).not.toHaveProperty('subType'); }); }); + + it('sets default field formatter', () => { + const fields = readFieldCapsResponse({ + fields: { + seconds: { + long: { + searchable: true, + aggregatable: true, + meta: { + unit: ['s'], + }, + }, + }, + }, + }); + expect(fields[0].defaultFormatter).toEqual('s'); + }); }); }); diff --git a/src/plugins/data_views/server/fetcher/lib/field_capabilities/field_caps_response.ts b/src/plugins/data_views/server/fetcher/lib/field_capabilities/field_caps_response.ts index 715fea9beef3b..805dbb9deb5fd 100644 --- a/src/plugins/data_views/server/fetcher/lib/field_capabilities/field_caps_response.ts +++ b/src/plugins/data_views/server/fetcher/lib/field_capabilities/field_caps_response.ts @@ -12,6 +12,11 @@ import { castEsToKbnFieldTypeName } from '@kbn/field-types'; import { shouldReadFieldFromDocValues } from './should_read_field_from_doc_values'; import { FieldDescriptor } from '../..'; +// The array will have different values if values vary across indices +const unitsArrayToFormatter = (unitArr: string[]) => { + return unitArr.find((unit) => unitArr[0] !== unit) ? undefined : unitArr[0]; +}; + /** * Read the response from the _field_caps API to determine the type and * "aggregatable"/"searchable" status of each field. @@ -134,7 +139,11 @@ export function readFieldCapsResponse( timeSeriesMetricType = 'position'; } const esType = types[0]; - const field = { + + const defaultFormatter = + capsByType[types[0]].meta?.unit && unitsArrayToFormatter(capsByType[types[0]].meta?.unit); + + const field: FieldDescriptor = { name: fieldName, type: castEsToKbnFieldTypeName(esType), esTypes: types, @@ -147,6 +156,11 @@ export function readFieldCapsResponse( timeSeriesMetric: timeSeriesMetricType, timeSeriesDimension: capsByType[types[0]].time_series_dimension, }; + + if (defaultFormatter) { + field.defaultFormatter = defaultFormatter; + } + // This is intentionally using a "hash" and a "push" to be highly optimized with very large indexes agg.array.push(field); agg.hash[fieldName] = field; diff --git a/src/plugins/data_views/server/rest_api_routes/internal/fields_for.ts b/src/plugins/data_views/server/rest_api_routes/internal/fields_for.ts index 98b4086bf161e..1d5f8e636315a 100644 --- a/src/plugins/data_views/server/rest_api_routes/internal/fields_for.ts +++ b/src/plugins/data_views/server/rest_api_routes/internal/fields_for.ts @@ -99,6 +99,7 @@ const FieldDescriptorSchema = schema.object({ conflictDescriptions: schema.maybe( schema.recordOf(schema.string(), schema.arrayOf(schema.string())) ), + defaultFormatter: schema.maybe(schema.string()), }); export const validate: FullValidationConfig = { diff --git a/src/plugins/discover/public/application/main/hooks/use_text_based_query_language.ts b/src/plugins/discover/public/application/main/hooks/use_text_based_query_language.ts index 1540fe9f3fe8c..49e010d13a041 100644 --- a/src/plugins/discover/public/application/main/hooks/use_text_based_query_language.ts +++ b/src/plugins/discover/public/application/main/hooks/use_text_based_query_language.ts @@ -18,7 +18,7 @@ import { FetchStatus } from '../../types'; const MAX_NUM_OF_COLUMNS = 50; // For ES|QL we want in case of the following commands to display a table view, otherwise display a document view -const TRANSFORMATIONAL_COMMANDS = ['stats', 'project', 'keep']; +const TRANSFORMATIONAL_COMMANDS = ['stats', 'keep']; /** * Hook to take care of text based query language state transformations when a new result is returned diff --git a/src/plugins/embeddable/public/add_panel_flyout/add_panel_flyout.test.tsx b/src/plugins/embeddable/public/add_panel_flyout/add_panel_flyout.test.tsx index a39f4ee95f15b..3b8052c88a798 100644 --- a/src/plugins/embeddable/public/add_panel_flyout/add_panel_flyout.test.tsx +++ b/src/plugins/embeddable/public/add_panel_flyout/add_panel_flyout.test.tsx @@ -68,7 +68,7 @@ describe('add panel flyout', () => { getEmbeddableFactory: embeddableStart.getEmbeddableFactory, } ); - container.addNewEmbeddable = jest.fn(); + container.addNewEmbeddable = jest.fn().mockResolvedValue({ id: 'foo' }); }); test('add panel flyout renders SavedObjectFinder', async () => { diff --git a/src/plugins/embeddable/public/add_panel_flyout/add_panel_flyout.tsx b/src/plugins/embeddable/public/add_panel_flyout/add_panel_flyout.tsx index f0554fed61782..a407fb025e2c8 100644 --- a/src/plugins/embeddable/public/add_panel_flyout/add_panel_flyout.tsx +++ b/src/plugins/embeddable/public/add_panel_flyout/add_panel_flyout.tsx @@ -32,6 +32,7 @@ import { SavedObjectEmbeddableInput, EmbeddableFactoryNotFoundError, } from '../lib'; +import { savedObjectToPanel } from '../registry/saved_object_to_panel_methods'; type FactoryMap = { [key: string]: EmbeddableFactory }; @@ -101,12 +102,30 @@ export const AddPanelFlyout = ({ throw new EmbeddableFactoryNotFoundError(type); } - const embeddable = await container.addNewEmbeddable( - factoryForSavedObjectType.type, - { savedObjectId: id }, - savedObject.attributes - ); - onAddPanel?.(embeddable.id); + let embeddableId: string; + + if (savedObjectToPanel[type]) { + // this panel type has a custom method for converting saved objects to panels + const panel = savedObjectToPanel[type](savedObject); + + const { id: _embeddableId } = await container.addNewEmbeddable( + factoryForSavedObjectType.type, + panel, + savedObject.attributes + ); + + embeddableId = _embeddableId; + } else { + const { id: _embeddableId } = await container.addNewEmbeddable( + factoryForSavedObjectType.type, + { savedObjectId: id }, + savedObject.attributes + ); + + embeddableId = _embeddableId; + } + + onAddPanel?.(embeddableId); showSuccessToast(name); runAddTelemetry(container.type, factoryForSavedObjectType, savedObject); @@ -136,6 +155,14 @@ export const AddPanelFlyout = ({ noItemsMessage={i18n.translate('embeddableApi.addPanel.noMatchingObjectsMessage', { defaultMessage: 'No matching objects found.', })} + getTooltipText={(item) => { + return item.managed + ? i18n.translate('embeddableApi.addPanel.managedPanelTooltip', { + defaultMessage: + 'This panel is managed by Elastic. It can be added but will be unlinked from the library.', + }) + : undefined; + }} /> diff --git a/src/plugins/embeddable/public/index.ts b/src/plugins/embeddable/public/index.ts index 5f9254ae11748..2eec70d591db3 100644 --- a/src/plugins/embeddable/public/index.ts +++ b/src/plugins/embeddable/public/index.ts @@ -113,6 +113,8 @@ export { serializeReactEmbeddableTitles, } from './react_embeddable_system'; +export { registerSavedObjectToPanelMethod } from './registry/saved_object_to_panel_methods'; + export function plugin(initializerContext: PluginInitializerContext) { return new EmbeddablePublicPlugin(initializerContext); } diff --git a/src/plugins/embeddable/public/lib/containers/container.ts b/src/plugins/embeddable/public/lib/containers/container.ts index d039bd5b80f89..9f3ac0f8d38a4 100644 --- a/src/plugins/embeddable/public/lib/containers/container.ts +++ b/src/plugins/embeddable/public/lib/containers/container.ts @@ -131,6 +131,16 @@ export abstract class Container< this.removeEmbeddable(id); } + public async addNewPanel( + panelPackage: PanelPackage + ): Promise { + const newEmbeddable = await this.addNewEmbeddable( + panelPackage.panelType, + panelPackage.initialState as Partial + ); + return newEmbeddable as ApiType; + } + public async replacePanel(idToRemove: string, { panelType, initialState }: PanelPackage) { return await this.replaceEmbeddable( idToRemove, diff --git a/src/plugins/embeddable/public/registry/saved_object_to_panel_methods.ts b/src/plugins/embeddable/public/registry/saved_object_to_panel_methods.ts new file mode 100644 index 0000000000000..2d996666d4042 --- /dev/null +++ b/src/plugins/embeddable/public/registry/saved_object_to_panel_methods.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. + */ + +import { SavedObjectCommon } from '@kbn/saved-objects-finder-plugin/common'; + +type SavedObjectToPanelMethod = ( + savedObject: SavedObjectCommon +) => { savedObjectId: string } | Partial; + +export const savedObjectToPanel: Record> = {}; + +export const registerSavedObjectToPanelMethod = ( + savedObjectType: string, + method: SavedObjectToPanelMethod +) => { + savedObjectToPanel[savedObjectType] = method; +}; diff --git a/src/plugins/kibana_react/public/url_template_editor/styles.scss b/src/plugins/kibana_react/public/url_template_editor/styles.scss index 99379b21454ec..1bff881958076 100644 --- a/src/plugins/kibana_react/public/url_template_editor/styles.scss +++ b/src/plugins/kibana_react/public/url_template_editor/styles.scss @@ -1,5 +1,5 @@ .urlTemplateEditor__container { .monaco-editor .lines-content.monaco-editor-background { - margin: $euiSizeS; + margin: 0 $euiSizeS; } } diff --git a/src/plugins/kibana_react/public/url_template_editor/url_template_editor.tsx b/src/plugins/kibana_react/public/url_template_editor/url_template_editor.tsx index 770753bc8c35c..13773396eba76 100644 --- a/src/plugins/kibana_react/public/url_template_editor/url_template_editor.tsx +++ b/src/plugins/kibana_react/public/url_template_editor/url_template_editor.tsx @@ -22,6 +22,7 @@ export interface UrlTemplateEditorVariable { export interface UrlTemplateEditorProps { value: string; height?: CodeEditorProps['height']; + fitToContent?: CodeEditorProps['fitToContent']; variables?: UrlTemplateEditorVariable[]; onChange: CodeEditorProps['onChange']; onEditor?: (editor: monaco.editor.IStandaloneCodeEditor) => void; @@ -31,6 +32,7 @@ export interface UrlTemplateEditorProps { export const UrlTemplateEditor: React.FC = ({ height = 105, + fitToContent, value, variables, onChange, @@ -127,6 +129,7 @@ export const UrlTemplateEditor: React.FC = ({ = ({ }, wordWrap: 'on', wrappingIndent: 'none', + automaticLayout: true, + scrollBeyondLastLine: false, + overviewRulerLanes: 0, + padding: { top: 8, bottom: 8 }, }} /> diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts index a527feefa754b..12e419822c5c8 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts @@ -596,6 +596,10 @@ export const stackManagementSchema: MakeSchemaFrom = { type: 'boolean', _meta: { description: 'Non-default value of setting.' }, }, + 'observability:enableInfrastructureHostsCustomDashboards': { + type: 'boolean', + _meta: { description: 'Non-default value of setting.' }, + }, 'securitySolution:enableGroupedNav': { type: 'boolean', _meta: { description: 'Non-default value of setting.' }, diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts index 6c5945a9f6805..f850103afb14b 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts @@ -47,6 +47,7 @@ export interface UsageStats { 'observability:apmAWSLambdaRequestCostPerMillion': number; 'observability:enableInfrastructureHostsView': boolean; 'observability:enableInfrastructureProfilingIntegration': boolean; + 'observability:enableInfrastructureHostsCustomDashboards': boolean; 'observability:apmAgentExplorerView': boolean; 'observability:apmEnableTableSearchBar': boolean; 'visualization:heatmap:maxBuckets': number; diff --git a/src/plugins/saved_objects_finder/public/finder/saved_object_finder.test.tsx b/src/plugins/saved_objects_finder/public/finder/saved_object_finder.test.tsx index 8ac5715263e30..c3e143ddbac42 100644 --- a/src/plugins/saved_objects_finder/public/finder/saved_object_finder.test.tsx +++ b/src/plugins/saved_objects_finder/public/finder/saved_object_finder.test.tsx @@ -9,7 +9,12 @@ const nextTick = () => new Promise((res) => process.nextTick(res)); import lodash from 'lodash'; -jest.spyOn(lodash, 'debounce').mockImplementation((fn: any) => fn); +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +jest.spyOn(lodash, 'debounce').mockImplementation((fn: any) => { + fn.cancel = jest.fn(); + return fn; +}); import { EuiInMemoryTable, EuiLink, @@ -962,4 +967,36 @@ describe('SavedObjectsFinder', () => { expect(findTestSubject(wrapper, 'tableHeaderCell_references_2')).toHaveLength(0); }); }); + + it('should add a tooltip when text is provided', async () => { + (contentClient.mSearch as any as jest.SpyInstance).mockResolvedValue({ + hits: [doc, doc2, doc3], + }); + + const tooltipText = 'This is a tooltip'; + + render( + (item.id === doc3.id ? tooltipText : undefined)} + /> + ); + + const assertTooltip = async (linkTitle: string, show: boolean) => { + const elem = await screen.findByText(linkTitle); + userEvent.hover(elem); + + const tooltip = screen.queryByText(tooltipText); + if (show) { + expect(tooltip).toBeInTheDocument(); + } else { + expect(tooltip).toBeNull(); + } + }; + + assertTooltip(doc.attributes.title, false); + assertTooltip(doc2.attributes.title, false); + assertTooltip(doc3.attributes.title, true); + }); }); diff --git a/src/plugins/saved_objects_finder/public/finder/saved_object_finder.tsx b/src/plugins/saved_objects_finder/public/finder/saved_object_finder.tsx index d660a8bf2857a..034f63083be1d 100644 --- a/src/plugins/saved_objects_finder/public/finder/saved_object_finder.tsx +++ b/src/plugins/saved_objects_finder/public/finder/saved_object_finder.tsx @@ -77,6 +77,7 @@ interface BaseSavedObjectFinder { leftChildren?: ReactElement | ReactElement[]; children?: ReactElement | ReactElement[]; helpText?: string; + getTooltipText?: (item: SavedObjectFinderItem) => string | undefined; } interface SavedObjectFinderFixedPage extends BaseSavedObjectFinder { @@ -288,7 +289,7 @@ export class SavedObjectFinderUi extends React.Component< ? currentSavedObjectMetaData.getTooltipForSavedObject(item.simple) : `${item.name} (${currentSavedObjectMetaData!.name})`; - return ( + const link = ( ); + + const tooltipText = this.props.getTooltipText?.(item); + + return tooltipText ? ( + + {link} + + ) : ( + link + ); }, }, ...(tagColumn ? [tagColumn] : []), diff --git a/src/plugins/saved_search/public/plugin.ts b/src/plugins/saved_search/public/plugin.ts index 8e8dd697cc55c..5b2d1e4e79fbf 100644 --- a/src/plugins/saved_search/public/plugin.ts +++ b/src/plugins/saved_search/public/plugin.ts @@ -17,13 +17,14 @@ import type { ContentManagementPublicStart, } from '@kbn/content-management-plugin/public'; import type { SOWithMetadata } from '@kbn/content-management-utils'; -import type { EmbeddableStart } from '@kbn/embeddable-plugin/public'; +import { EmbeddableStart, registerSavedObjectToPanelMethod } from '@kbn/embeddable-plugin/public'; import { getSavedSearch, saveSavedSearch, SaveSavedSearchOptions, getNewSavedSearch, SavedSearchUnwrapResult, + SearchByValueInput, } from './services/saved_searches'; import { SavedSearch, SavedSearchAttributes } from '../common/types'; import { SavedSearchType, LATEST_VERSION } from '../common'; @@ -35,6 +36,7 @@ import { getSavedSearchAttributeService, toSavedSearch, } from './services/saved_searches'; +import { savedObjectToEmbeddableAttributes } from './services/saved_searches/saved_search_attribute_service'; /** * Saved search plugin public Setup contract @@ -115,6 +117,19 @@ export class SavedSearchPublicPlugin expressions.registerType(kibanaContext); + registerSavedObjectToPanelMethod( + SavedSearchType, + (savedObject) => { + if (!savedObject.managed) { + return { savedObjectId: savedObject.id }; + } + + return { + attributes: savedObjectToEmbeddableAttributes(savedObject), + }; + } + ); + return {}; } diff --git a/src/plugins/saved_search/public/services/saved_searches/saved_search_attribute_service.ts b/src/plugins/saved_search/public/services/saved_searches/saved_search_attribute_service.ts index aff78e18b6bd1..726853d26ebe4 100644 --- a/src/plugins/saved_search/public/services/saved_searches/saved_search_attribute_service.ts +++ b/src/plugins/saved_search/public/services/saved_searches/saved_search_attribute_service.ts @@ -9,6 +9,8 @@ import type { AttributeService, EmbeddableStart } from '@kbn/embeddable-plugin/public'; import type { OnSaveProps } from '@kbn/saved-objects-plugin/public'; import { SEARCH_EMBEDDABLE_TYPE } from '@kbn/discover-utils'; +import { SavedObjectCommon } from '@kbn/saved-objects-finder-plugin/common'; +import { SavedSearchAttributes } from '../../../common'; import type { SavedSearch, SavedSearchByValueAttributes, @@ -41,6 +43,13 @@ export type SavedSearchAttributeService = AttributeService< SavedSearchUnwrapMetaInfo >; +export const savedObjectToEmbeddableAttributes = ( + savedObject: SavedObjectCommon +) => ({ + ...savedObject.attributes, + references: savedObject.references, +}); + export function getSavedSearchAttributeService( services: SavedSearchesServiceDeps & { embeddable: EmbeddableStart; @@ -67,10 +76,7 @@ export function getSavedSearchAttributeService( const so = await getSearchSavedObject(savedObjectId, createGetSavedSearchDeps(services)); return { - attributes: { - ...so.item.attributes, - references: so.item.references, - }, + attributes: savedObjectToEmbeddableAttributes(so.item), metaInfo: { sharingSavedObjectProps: so.meta, managed: so.item.managed, diff --git a/src/plugins/saved_search/tsconfig.json b/src/plugins/saved_search/tsconfig.json index b1aa1679469ee..c4ea0eaf4d5bc 100644 --- a/src/plugins/saved_search/tsconfig.json +++ b/src/plugins/saved_search/tsconfig.json @@ -32,6 +32,7 @@ "@kbn/logging", "@kbn/core-plugins-server", "@kbn/utility-types", + "@kbn/saved-objects-finder-plugin", ], "exclude": [ "target/**/*", diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json index 23239ab0b0ca8..659c791645ca9 100644 --- a/src/plugins/telemetry/schema/oss_plugins.json +++ b/src/plugins/telemetry/schema/oss_plugins.json @@ -10210,6 +10210,12 @@ "description": "Non-default value of setting." } }, + "observability:enableInfrastructureHostsCustomDashboards": { + "type": "boolean", + "_meta": { + "description": "Non-default value of setting." + } + }, "securitySolution:enableGroupedNav": { "type": "boolean", "_meta": { diff --git a/src/plugins/ui_actions_enhanced/public/drilldowns/url_drilldown/components/url_drilldown_collect_config/url_drilldown_collect_config.tsx b/src/plugins/ui_actions_enhanced/public/drilldowns/url_drilldown/components/url_drilldown_collect_config/url_drilldown_collect_config.tsx index 0495f2d61063c..4db2510ba22e5 100644 --- a/src/plugins/ui_actions_enhanced/public/drilldowns/url_drilldown/components/url_drilldown_collect_config/url_drilldown_collect_config.tsx +++ b/src/plugins/ui_actions_enhanced/public/drilldowns/url_drilldown/components/url_drilldown_collect_config/url_drilldown_collect_config.tsx @@ -88,6 +88,7 @@ export const UrlDrilldownCollectConfig: React.FC labelAppend={variablesDropdown} > { let queryHasTransformationalCommands = false; diff --git a/src/plugins/vis_default_editor/public/components/controls/palette_picker.tsx b/src/plugins/vis_default_editor/public/components/controls/palette_picker.tsx index 29d8924a25077..6155cdbd24cb6 100644 --- a/src/plugins/vis_default_editor/public/components/controls/palette_picker.tsx +++ b/src/plugins/vis_default_editor/public/components/controls/palette_picker.tsx @@ -8,6 +8,7 @@ import React from 'react'; import type { PaletteOutput, PaletteRegistry } from '@kbn/coloring'; +import { getActivePaletteName } from '@kbn/coloring'; import { EuiColorPalettePicker, EuiColorPalettePickerPaletteProps } from '@elastic/eui'; import { EuiFormRow } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -27,6 +28,8 @@ export function PalettePicker({ paramName, setPalette, }: PalettePickerProps) { + const paletteName = getActivePaletteName(activePalette?.name); + const palettesList: EuiColorPalettePickerPaletteProps[] = palettes .getAll() .filter(({ internal }) => !internal) @@ -35,10 +38,7 @@ export function PalettePicker({ value: id, title, type: 'fixed', - palette: getCategoricalColors( - 10, - id === activePalette?.name ? activePalette?.params : undefined - ), + palette: getCategoricalColors(10, id === paletteName ? activePalette?.params : undefined), }; }); @@ -61,7 +61,7 @@ export function PalettePicker({ name: palette?.value ?? DEFAULT_PALETTE, }); }} - valueOfSelected={activePalette?.name || DEFAULT_PALETTE} + valueOfSelected={paletteName} selectionDisplay={'palette'} /> diff --git a/src/plugins/vis_types/timeseries/public/application/components/palette_picker.tsx b/src/plugins/vis_types/timeseries/public/application/components/palette_picker.tsx index 56d763ad64eb5..cd459de244f84 100644 --- a/src/plugins/vis_types/timeseries/public/application/components/palette_picker.tsx +++ b/src/plugins/vis_types/timeseries/public/application/components/palette_picker.tsx @@ -8,6 +8,7 @@ import React from 'react'; import type { PaletteRegistry, PaletteOutput } from '@kbn/coloring'; +import { getActivePaletteName } from '@kbn/coloring'; import { EuiColorPalettePicker } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { rainbowColors } from '../lib/rainbow_colors'; @@ -23,6 +24,7 @@ export interface PalettePickerProps { export function PalettePicker({ activePalette, palettes, setPalette, color }: PalettePickerProps) { const finalGradientColor = computeGradientFinalColor(color); + const paletteName = getActivePaletteName(activePalette?.name); return ( ); diff --git a/src/plugins/visualizations/public/plugin.ts b/src/plugins/visualizations/public/plugin.ts index cd840302ff01d..594ea6ca9488a 100644 --- a/src/plugins/visualizations/public/plugin.ts +++ b/src/plugins/visualizations/public/plugin.ts @@ -47,7 +47,11 @@ import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; import type { DataPublicPluginSetup, DataPublicPluginStart } from '@kbn/data-plugin/public'; import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import type { ExpressionsSetup, ExpressionsStart } from '@kbn/expressions-plugin/public'; -import type { EmbeddableSetup, EmbeddableStart } from '@kbn/embeddable-plugin/public'; +import { + EmbeddableSetup, + EmbeddableStart, + registerSavedObjectToPanelMethod, +} from '@kbn/embeddable-plugin/public'; import type { SavedObjectTaggingOssPluginStart } from '@kbn/saved-objects-tagging-oss-plugin/public'; import type { NavigationPublicPluginStart as NavigationStart } from '@kbn/navigation-plugin/public'; import type { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/public'; @@ -112,8 +116,14 @@ import { } from './services'; import { VisualizeConstants } from '../common/constants'; import { EditInLensAction } from './actions/edit_in_lens_action'; -import { ListingViewRegistry } from './types'; -import { LATEST_VERSION, CONTENT_ID } from '../common/content_management'; +import { ListingViewRegistry, SerializedVis } from './types'; +import { + LATEST_VERSION, + CONTENT_ID, + VisualizationSavedObjectAttributes, +} from '../common/content_management'; +import { SerializedVisData } from '../common'; +import { VisualizeByValueInput } from './embeddable/visualize_embeddable'; /** * Interface for this plugin's returned setup/start contracts. @@ -397,6 +407,37 @@ export class VisualizationsPlugin name: 'Visualize Library', }); + registerSavedObjectToPanelMethod( + CONTENT_ID, + (savedObject) => { + const visState = savedObject.attributes.visState; + + // not sure if visState actually is ever undefined, but following the type + if (!savedObject.managed || !visState) { + return { + savedObjectId: savedObject.id, + }; + } + + // data is not always defined, so I added a default value since the extract + // routine in the embeddable factory expects it to be there + const savedVis = JSON.parse(visState) as Omit & { + data?: SerializedVisData; + }; + + if (!savedVis.data) { + savedVis.data = { + searchSource: {}, + aggs: [], + }; + } + + return { + savedVis: savedVis as SerializedVis, // now we're sure we have "data" prop + }; + } + ); + return { ...this.types.setup(), visEditorsRegistry, diff --git a/test/api_integration/apis/search/bsearch.ts b/test/api_integration/apis/search/bsearch.ts index 867ae83864a74..96b4bbbf622cf 100644 --- a/test/api_integration/apis/search/bsearch.ts +++ b/test/api_integration/apis/search/bsearch.ts @@ -428,7 +428,6 @@ export default function ({ getService }: FtrProviderContext) { expect(jsonBody[0].result.requestParams).to.eql({ method: 'POST', path: '/_query', - querystring: 'drop_null_columns', }); }); @@ -457,7 +456,6 @@ export default function ({ getService }: FtrProviderContext) { expect(jsonBody[0].error.attributes.requestParams).to.eql({ method: 'POST', path: '/_query', - querystring: 'drop_null_columns', }); }); }); diff --git a/test/functional/apps/bundles/index.js b/test/functional/apps/bundles/index.js index aa175f16e5d48..7363d9f0b5256 100644 --- a/test/functional/apps/bundles/index.js +++ b/test/functional/apps/bundles/index.js @@ -14,38 +14,38 @@ export default function ({ getService }) { const supertest = getService('supertest'); describe('bundle compression', function () { - let buildNum; + let buildHash; before(async () => { const resp = await supertest.get('/api/status').expect(200); - buildNum = resp.body.version.build_number; + buildHash = resp.body.version.build_hash.slice(0, 12); }); it('returns gzip files when client only supports gzip', () => supertest // We use the kbn-ui-shared-deps for these tests since they are always built with br compressed outputs, // even in dev. Bundles built by @kbn/optimizer are only built with br compression in dist mode. - .get(`/${buildNum}/bundles/kbn-ui-shared-deps-npm/kbn-ui-shared-deps-npm.dll.js`) + .get(`/${buildHash}/bundles/kbn-ui-shared-deps-npm/kbn-ui-shared-deps-npm.dll.js`) .set('Accept-Encoding', 'gzip') .expect(200) .expect('Content-Encoding', 'gzip')); it('returns br files when client only supports br', () => supertest - .get(`/${buildNum}/bundles/kbn-ui-shared-deps-npm/kbn-ui-shared-deps-npm.dll.js`) + .get(`/${buildHash}/bundles/kbn-ui-shared-deps-npm/kbn-ui-shared-deps-npm.dll.js`) .set('Accept-Encoding', 'br') .expect(200) .expect('Content-Encoding', 'br')); it('returns br files when client only supports gzip and br', () => supertest - .get(`/${buildNum}/bundles/kbn-ui-shared-deps-npm/kbn-ui-shared-deps-npm.dll.js`) + .get(`/${buildHash}/bundles/kbn-ui-shared-deps-npm/kbn-ui-shared-deps-npm.dll.js`) .set('Accept-Encoding', 'gzip, br') .expect(200) .expect('Content-Encoding', 'br')); it('returns gzip files when client prefers gzip', () => supertest - .get(`/${buildNum}/bundles/kbn-ui-shared-deps-npm/kbn-ui-shared-deps-npm.dll.js`) + .get(`/${buildHash}/bundles/kbn-ui-shared-deps-npm/kbn-ui-shared-deps-npm.dll.js`) .set('Accept-Encoding', 'gzip;q=1.0, br;q=0.5') .expect(200) .expect('Content-Encoding', 'gzip')); 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 4c8873f59b348..651dbce7ada02 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 @@ -20,7 +20,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const es = getService('es'); const PageObjects = getPageObjects(['settings', 'common', 'header']); - describe('creating and deleting default data view', function describeIndexTests() { + // FLAKY: https://github.com/elastic/kibana/issues/174066 + describe.skip('creating and deleting default data view', function describeIndexTests() { before(async function () { await esArchiver.emptyKibanaIndex(); await esArchiver.loadIfNeeded( @@ -149,8 +150,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); }); - // FLAKY: https://github.com/elastic/kibana/issues/174066 - describe.skip('edit index pattern', () => { + describe('edit index pattern', () => { it('on edit click', async () => { await PageObjects.settings.editIndexPattern('logstash-*', '@timestamp', 'Logstash Star'); diff --git a/test/functional/apps/management/data_views/_field_formatter.ts b/test/functional/apps/management/data_views/_field_formatter.ts index dfcdc0b877581..cddbe8ccf5ce4 100644 --- a/test/functional/apps/management/data_views/_field_formatter.ts +++ b/test/functional/apps/management/data_views/_field_formatter.ts @@ -413,6 +413,51 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ]); }); }); + + describe('default formatter by field meta value', () => { + const indexTitle = 'field_formats_management_functional_tests'; + + before(async () => { + if (await es.indices.exists({ index: indexTitle })) { + await es.indices.delete({ index: indexTitle }); + } + }); + + it('should apply default formatter by field meta value', async () => { + await es.indices.create({ + index: indexTitle, + body: { + mappings: { + properties: { + seconds: { type: 'long', meta: { unit: 's' } }, + }, + }, + }, + }); + + const docResult = await es.index({ + index: indexTitle, + body: { seconds: 1234 }, + refresh: 'wait_for', + }); + + const testDocumentId = docResult._id; + + const indexPatternResult = await indexPatterns.create( + { title: `${indexTitle}*` }, // sidesteps field caching when index pattern is reused + { override: true } + ); + + await PageObjects.common.navigateToApp('discover', { + hash: `/doc/${indexPatternResult.id}/${indexTitle}?id=${testDocumentId}`, + }); + await testSubjects.exists('doc-hit'); + + const renderedValue = await testSubjects.find(`tableDocViewRow-seconds-value`); + const text = await renderedValue.getVisibleText(); + expect(text).to.be('20.57 min'); + }); + }); }); /** diff --git a/test/functional/fixtures/kbn_archiver/managed_content.json b/test/functional/fixtures/kbn_archiver/managed_content.json index 9e46c515d6b8b..d2cafbe0c842e 100644 --- a/test/functional/fixtures/kbn_archiver/managed_content.json +++ b/test/functional/fixtures/kbn_archiver/managed_content.json @@ -1,17 +1,19 @@ {"attributes":{"allowHidden":false,"fieldAttrs":"{}","fieldFormatMap":"{}","fields":"[]","name":"logstash-*","runtimeFieldMap":"{}","sourceFilters":"[]","timeFieldName":"@timestamp","title":"logstash-*"},"coreMigrationVersion":"8.8.0","created_at":"2024-01-18T17:35:58.606Z","id":"5f863f70-4728-4e8d-b441-db08f8c33b28","managed":false,"references":[],"type":"index-pattern","typeMigrationVersion":"8.0.0","updated_at":"2024-01-18T17:35:58.606Z","version":"WzI4LDFd"} -{"attributes":{"description":"","state":{"adHocDataViews":{},"datasourceStates":{"formBased":{"layers":{"e633b1af-3ab4-4bf5-8faa-fefde06c4a4a":{"columnOrder":["f2555a1a-6f93-43fd-bc63-acdfadd47729","d229daf9-9658-4579-99af-01d8adb2f25f"],"columns":{"d229daf9-9658-4579-99af-01d8adb2f25f":{"dataType":"number","isBucketed":false,"label":"Median of bytes","operationType":"median","params":{"emptyAsNull":true},"scale":"ratio","sourceField":"bytes"},"f2555a1a-6f93-43fd-bc63-acdfadd47729":{"dataType":"date","isBucketed":true,"label":"@timestamp","operationType":"date_histogram","params":{"dropPartials":false,"includeEmptyRows":true,"interval":"auto"},"scale":"interval","sourceField":"@timestamp"}},"incompleteColumns":{},"sampling":1}}},"indexpattern":{"layers":{}},"textBased":{"layers":{}}},"filters":[],"internalReferences":[],"query":{"language":"kuery","query":""},"visualization":{"axisTitlesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"fittingFunction":"None","gridlinesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"labelsOrientation":{"x":0,"yLeft":0,"yRight":0},"layers":[{"accessors":["d229daf9-9658-4579-99af-01d8adb2f25f"],"layerId":"e633b1af-3ab4-4bf5-8faa-fefde06c4a4a","layerType":"data","position":"top","seriesType":"bar_stacked","showGridlines":false,"xAccessor":"f2555a1a-6f93-43fd-bc63-acdfadd47729"}],"legend":{"isVisible":true,"position":"right"},"preferredSeriesType":"bar_stacked","tickLabelsVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"valueLabels":"hide"}},"title":"Lens vis (managed)","visualizationType":"lnsXY"},"coreMigrationVersion":"8.8.0","created_at":"2024-01-18T17:42:12.920Z","id":"managed-36db-4a3b-a4ba-7a64ab8f130b","managed":true,"references":[{"id":"5f863f70-4728-4e8d-b441-db08f8c33b28","name":"indexpattern-datasource-layer-e633b1af-3ab4-4bf5-8faa-fefde06c4a4a","type":"index-pattern"}],"type":"lens","typeMigrationVersion":"8.9.0","updated_at":"2024-01-18T17:42:12.920Z","version":"WzQ1LDFd"} +{"attributes":{"description":"","state":{"adHocDataViews":{},"datasourceStates":{"formBased":{"layers":{"e633b1af-3ab4-4bf5-8faa-fefde06c4a4a":{"columnOrder":["f2555a1a-6f93-43fd-bc63-acdfadd47729","d229daf9-9658-4579-99af-01d8adb2f25f"],"columns":{"d229daf9-9658-4579-99af-01d8adb2f25f":{"dataType":"number","isBucketed":false,"label":"Median of bytes","operationType":"median","params":{"emptyAsNull":true},"scale":"ratio","sourceField":"bytes"},"f2555a1a-6f93-43fd-bc63-acdfadd47729":{"dataType":"date","isBucketed":true,"label":"@timestamp","operationType":"date_histogram","params":{"dropPartials":false,"includeEmptyRows":true,"interval":"auto"},"scale":"interval","sourceField":"@timestamp"}},"incompleteColumns":{},"sampling":1}}},"indexpattern":{"layers":{}},"textBased":{"layers":{}}},"filters":[],"internalReferences":[],"query":{"language":"kuery","query":""},"visualization":{"axisTitlesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"fittingFunction":"None","gridlinesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"labelsOrientation":{"x":0,"yLeft":0,"yRight":0},"layers":[{"accessors":["d229daf9-9658-4579-99af-01d8adb2f25f"],"layerId":"e633b1af-3ab4-4bf5-8faa-fefde06c4a4a","layerType":"data","position":"top","seriesType":"bar_stacked","showGridlines":false,"xAccessor":"f2555a1a-6f93-43fd-bc63-acdfadd47729"}],"legend":{"isVisible":true,"position":"right"},"preferredSeriesType":"bar_stacked","tickLabelsVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"valueLabels":"hide"}},"title":"Managed lens vis","visualizationType":"lnsXY"},"coreMigrationVersion":"8.8.0","created_at":"2024-01-18T17:42:12.920Z","id":"managed-36db-4a3b-a4ba-7a64ab8f130b","managed":true,"references":[{"id":"5f863f70-4728-4e8d-b441-db08f8c33b28","name":"indexpattern-datasource-layer-e633b1af-3ab4-4bf5-8faa-fefde06c4a4a","type":"index-pattern"}],"type":"lens","typeMigrationVersion":"8.9.0","updated_at":"2024-01-18T17:42:12.920Z","version":"WzQ1LDFd"} -{"attributes":{"description":"","state":{"adHocDataViews":{},"datasourceStates":{"formBased":{"layers":{"e633b1af-3ab4-4bf5-8faa-fefde06c4a4a":{"columnOrder":["f2555a1a-6f93-43fd-bc63-acdfadd47729","d229daf9-9658-4579-99af-01d8adb2f25f"],"columns":{"d229daf9-9658-4579-99af-01d8adb2f25f":{"dataType":"number","isBucketed":false,"label":"Median of bytes","operationType":"median","params":{"emptyAsNull":true},"scale":"ratio","sourceField":"bytes"},"f2555a1a-6f93-43fd-bc63-acdfadd47729":{"dataType":"date","isBucketed":true,"label":"@timestamp","operationType":"date_histogram","params":{"dropPartials":false,"includeEmptyRows":true,"interval":"auto"},"scale":"interval","sourceField":"@timestamp"}},"incompleteColumns":{},"sampling":1}}},"indexpattern":{"layers":{}},"textBased":{"layers":{}}},"filters":[],"internalReferences":[],"query":{"language":"kuery","query":""},"visualization":{"axisTitlesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"fittingFunction":"None","gridlinesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"labelsOrientation":{"x":0,"yLeft":0,"yRight":0},"layers":[{"accessors":["d229daf9-9658-4579-99af-01d8adb2f25f"],"layerId":"e633b1af-3ab4-4bf5-8faa-fefde06c4a4a","layerType":"data","position":"top","seriesType":"bar_stacked","showGridlines":false,"xAccessor":"f2555a1a-6f93-43fd-bc63-acdfadd47729"}],"legend":{"isVisible":true,"position":"right"},"preferredSeriesType":"bar_stacked","tickLabelsVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"valueLabels":"hide"}},"title":"Lens vis (unmanaged)","visualizationType":"lnsXY"},"coreMigrationVersion":"8.8.0","created_at":"2024-01-18T17:42:12.920Z","id":"unmanaged-36db-4a3b-a4ba-7a64ab8f130b","managed":false,"references":[{"id":"5f863f70-4728-4e8d-b441-db08f8c33b28","name":"indexpattern-datasource-layer-e633b1af-3ab4-4bf5-8faa-fefde06c4a4a","type":"index-pattern"}],"type":"lens","typeMigrationVersion":"8.9.0","updated_at":"2024-01-18T17:42:12.920Z","version":"WzQ1LDFd"} +{"attributes":{"description":"","state":{"adHocDataViews":{},"datasourceStates":{"formBased":{"layers":{"e633b1af-3ab4-4bf5-8faa-fefde06c4a4a":{"columnOrder":["f2555a1a-6f93-43fd-bc63-acdfadd47729","d229daf9-9658-4579-99af-01d8adb2f25f"],"columns":{"d229daf9-9658-4579-99af-01d8adb2f25f":{"dataType":"number","isBucketed":false,"label":"Median of bytes","operationType":"median","params":{"emptyAsNull":true},"scale":"ratio","sourceField":"bytes"},"f2555a1a-6f93-43fd-bc63-acdfadd47729":{"dataType":"date","isBucketed":true,"label":"@timestamp","operationType":"date_histogram","params":{"dropPartials":false,"includeEmptyRows":true,"interval":"auto"},"scale":"interval","sourceField":"@timestamp"}},"incompleteColumns":{},"sampling":1}}},"indexpattern":{"layers":{}},"textBased":{"layers":{}}},"filters":[],"internalReferences":[],"query":{"language":"kuery","query":""},"visualization":{"axisTitlesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"fittingFunction":"None","gridlinesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"labelsOrientation":{"x":0,"yLeft":0,"yRight":0},"layers":[{"accessors":["d229daf9-9658-4579-99af-01d8adb2f25f"],"layerId":"e633b1af-3ab4-4bf5-8faa-fefde06c4a4a","layerType":"data","position":"top","seriesType":"bar_stacked","showGridlines":false,"xAccessor":"f2555a1a-6f93-43fd-bc63-acdfadd47729"}],"legend":{"isVisible":true,"position":"right"},"preferredSeriesType":"bar_stacked","tickLabelsVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"valueLabels":"hide"}},"title":"Unmanaged lens vis","visualizationType":"lnsXY"},"coreMigrationVersion":"8.8.0","created_at":"2024-01-18T17:42:12.920Z","id":"unmanaged-36db-4a3b-a4ba-7a64ab8f130b","managed":false,"references":[{"id":"5f863f70-4728-4e8d-b441-db08f8c33b28","name":"indexpattern-datasource-layer-e633b1af-3ab4-4bf5-8faa-fefde06c4a4a","type":"index-pattern"}],"type":"lens","typeMigrationVersion":"8.9.0","updated_at":"2024-01-18T17:42:12.920Z","version":"WzQ1LDFd"} -{"attributes":{"columns":["@tags","clientip"],"description":"","grid":{},"hideChart":false,"isTextBasedQuery":false,"kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"agent.raw:\\\"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)\\\" \",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"sort":[["@timestamp","desc"]],"timeRestore":false,"title":"Saved search","usesAdHocDataView":false},"coreMigrationVersion":"8.8.0","created_at":"2024-01-22T18:11:05.016Z","id":"managed-3d62-4113-ac7c-de2e20a68fbc","managed":true,"references":[{"id":"5f863f70-4728-4e8d-b441-db08f8c33b28","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"}],"type":"search","typeMigrationVersion":"8.0.0","updated_at":"2024-01-22T18:11:05.016Z","version":"WzIzLDFd"} +{"attributes":{"columns":["@tags","clientip"],"description":"","grid":{},"hideChart":false,"isTextBasedQuery":false,"kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"agent.raw:\\\"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)\\\" \",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"sort":[["@timestamp","desc"]],"timeRestore":false,"title":"Managed saved search","usesAdHocDataView":false},"coreMigrationVersion":"8.8.0","created_at":"2024-01-22T18:11:05.016Z","id":"managed-3d62-4113-ac7c-de2e20a68fbc","managed":true,"references":[{"id":"5f863f70-4728-4e8d-b441-db08f8c33b28","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"}],"type":"search","typeMigrationVersion":"8.0.0","updated_at":"2024-01-22T18:11:05.016Z","version":"WzIzLDFd"} -{"attributes":{"columns":["@tags","clientip"],"description":"","grid":{},"hideChart":false,"isTextBasedQuery":false,"kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"agent.raw:\\\"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)\\\" \",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"sort":[["@timestamp","desc"]],"timeRestore":false,"title":"Saved search","usesAdHocDataView":false},"coreMigrationVersion":"8.8.0","created_at":"2024-01-22T18:11:05.016Z","id":"unmanaged-3d62-4113-ac7c-de2e20a68fbc","managed":false,"references":[{"id":"5f863f70-4728-4e8d-b441-db08f8c33b28","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"}],"type":"search","typeMigrationVersion":"8.0.0","updated_at":"2024-01-22T18:11:05.016Z","version":"WzIzLDFd"} +{"attributes":{"columns":["@tags","clientip"],"description":"","grid":{},"hideChart":false,"isTextBasedQuery":false,"kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"agent.raw:\\\"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)\\\" \",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"sort":[["@timestamp","desc"]],"timeRestore":false,"title":"Unmanaged saved search","usesAdHocDataView":false},"coreMigrationVersion":"8.8.0","created_at":"2024-01-22T18:11:05.016Z","id":"unmanaged-3d62-4113-ac7c-de2e20a68fbc","managed":false,"references":[{"id":"5f863f70-4728-4e8d-b441-db08f8c33b28","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"}],"type":"search","typeMigrationVersion":"8.0.0","updated_at":"2024-01-22T18:11:05.016Z","version":"WzIzLDFd"} -{"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}"},"title":"Legacy visualization","uiStateJSON":"{}","version":1,"visState":"{\"title\":\"Legacy visualization\",\"type\":\"metrics\",\"aggs\":[],\"params\":{\"id\":\"1a14d0ad-0d74-4470-a189-8f040cddc1a1\",\"type\":\"timeseries\",\"series\":[{\"id\":\"daa8bbf7-86cc-4394-b249-be48da9f7351\",\"color\":\"#68BC00\",\"split_mode\":\"everything\",\"palette\":{\"type\":\"palette\",\"name\":\"default\"},\"metrics\":[{\"id\":\"795375d9-1aa6-454d-9b23-687e69f3382c\",\"type\":\"count\"}],\"separate_axis\":0,\"axis_position\":\"right\",\"formatter\":\"default\",\"chart_type\":\"line\",\"line_width\":1,\"point_size\":1,\"fill\":0.5,\"stacked\":\"none\",\"override_index_pattern\":0,\"series_drop_last_bucket\":0}],\"time_field\":\"\",\"use_kibana_indexes\":true,\"interval\":\"\",\"axis_position\":\"left\",\"axis_formatter\":\"number\",\"axis_scale\":\"normal\",\"show_legend\":1,\"truncate_legend\":1,\"max_lines_legend\":1,\"show_grid\":1,\"tooltip_mode\":\"show_all\",\"drop_last_bucket\":0,\"index_pattern_ref_name\":\"metrics_0_index_pattern\"}}"},"coreMigrationVersion":"8.8.0","created_at":"2024-01-24T18:53:06.818Z","id":"managed-feb9-4ba6-9538-1b8f67fb4f57","managed":true,"references":[{"id":"5f863f70-4728-4e8d-b441-db08f8c33b28","name":"metrics_0_index_pattern","type":"index-pattern"}],"type":"visualization","typeMigrationVersion":"8.5.0","updated_at":"2024-01-24T18:53:06.818Z","version":"WzEzLDFd"} +{"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}"},"title":"Managed legacy visualization","uiStateJSON":"{}","version":1,"visState":"{\"title\":\"Legacy visualization\",\"type\":\"metrics\",\"aggs\":[],\"params\":{\"id\":\"1a14d0ad-0d74-4470-a189-8f040cddc1a1\",\"type\":\"timeseries\",\"series\":[{\"id\":\"daa8bbf7-86cc-4394-b249-be48da9f7351\",\"color\":\"#68BC00\",\"split_mode\":\"everything\",\"palette\":{\"type\":\"palette\",\"name\":\"default\"},\"metrics\":[{\"id\":\"795375d9-1aa6-454d-9b23-687e69f3382c\",\"type\":\"count\"}],\"separate_axis\":0,\"axis_position\":\"right\",\"formatter\":\"default\",\"chart_type\":\"line\",\"line_width\":1,\"point_size\":1,\"fill\":0.5,\"stacked\":\"none\",\"override_index_pattern\":0,\"series_drop_last_bucket\":0}],\"time_field\":\"\",\"use_kibana_indexes\":true,\"interval\":\"\",\"axis_position\":\"left\",\"axis_formatter\":\"number\",\"axis_scale\":\"normal\",\"show_legend\":1,\"truncate_legend\":1,\"max_lines_legend\":1,\"show_grid\":1,\"tooltip_mode\":\"show_all\",\"drop_last_bucket\":0,\"index_pattern_ref_name\":\"metrics_0_index_pattern\"}}"},"coreMigrationVersion":"8.8.0","created_at":"2024-01-24T18:53:06.818Z","id":"managed-feb9-4ba6-9538-1b8f67fb4f57","managed":true,"references":[{"id":"5f863f70-4728-4e8d-b441-db08f8c33b28","name":"metrics_0_index_pattern","type":"index-pattern"}],"type":"visualization","typeMigrationVersion":"8.5.0","updated_at":"2024-01-24T18:53:06.818Z","version":"WzEzLDFd"} -{"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}"},"title":"Legacy visualization","uiStateJSON":"{}","version":1,"visState":"{\"title\":\"Legacy visualization\",\"type\":\"metrics\",\"aggs\":[],\"params\":{\"id\":\"1a14d0ad-0d74-4470-a189-8f040cddc1a1\",\"type\":\"timeseries\",\"series\":[{\"id\":\"daa8bbf7-86cc-4394-b249-be48da9f7351\",\"color\":\"#68BC00\",\"split_mode\":\"everything\",\"palette\":{\"type\":\"palette\",\"name\":\"default\"},\"metrics\":[{\"id\":\"795375d9-1aa6-454d-9b23-687e69f3382c\",\"type\":\"count\"}],\"separate_axis\":0,\"axis_position\":\"right\",\"formatter\":\"default\",\"chart_type\":\"line\",\"line_width\":1,\"point_size\":1,\"fill\":0.5,\"stacked\":\"none\",\"override_index_pattern\":0,\"series_drop_last_bucket\":0}],\"time_field\":\"\",\"use_kibana_indexes\":true,\"interval\":\"\",\"axis_position\":\"left\",\"axis_formatter\":\"number\",\"axis_scale\":\"normal\",\"show_legend\":1,\"truncate_legend\":1,\"max_lines_legend\":1,\"show_grid\":1,\"tooltip_mode\":\"show_all\",\"drop_last_bucket\":0,\"index_pattern_ref_name\":\"metrics_0_index_pattern\"}}"},"coreMigrationVersion":"8.8.0","created_at":"2024-01-24T18:53:06.818Z","id":"unmanaged-feb9-4ba6-9538-1b8f67fb4f57","managed":false,"references":[{"id":"5f863f70-4728-4e8d-b441-db08f8c33b28","name":"metrics_0_index_pattern","type":"index-pattern"}],"type":"visualization","typeMigrationVersion":"8.5.0","updated_at":"2024-01-24T18:53:06.818Z","version":"WzEzLDFd"} +{"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}"},"title":"Unmanaged legacy visualization","uiStateJSON":"{}","version":1,"visState":"{\"title\":\"Legacy visualization\",\"type\":\"metrics\",\"aggs\":[],\"params\":{\"id\":\"1a14d0ad-0d74-4470-a189-8f040cddc1a1\",\"type\":\"timeseries\",\"series\":[{\"id\":\"daa8bbf7-86cc-4394-b249-be48da9f7351\",\"color\":\"#68BC00\",\"split_mode\":\"everything\",\"palette\":{\"type\":\"palette\",\"name\":\"default\"},\"metrics\":[{\"id\":\"795375d9-1aa6-454d-9b23-687e69f3382c\",\"type\":\"count\"}],\"separate_axis\":0,\"axis_position\":\"right\",\"formatter\":\"default\",\"chart_type\":\"line\",\"line_width\":1,\"point_size\":1,\"fill\":0.5,\"stacked\":\"none\",\"override_index_pattern\":0,\"series_drop_last_bucket\":0}],\"time_field\":\"\",\"use_kibana_indexes\":true,\"interval\":\"\",\"axis_position\":\"left\",\"axis_formatter\":\"number\",\"axis_scale\":\"normal\",\"show_legend\":1,\"truncate_legend\":1,\"max_lines_legend\":1,\"show_grid\":1,\"tooltip_mode\":\"show_all\",\"drop_last_bucket\":0,\"index_pattern_ref_name\":\"metrics_0_index_pattern\"}}"},"coreMigrationVersion":"8.8.0","created_at":"2024-01-24T18:53:06.818Z","id":"unmanaged-feb9-4ba6-9538-1b8f67fb4f57","managed":false,"references":[{"id":"5f863f70-4728-4e8d-b441-db08f8c33b28","name":"metrics_0_index_pattern","type":"index-pattern"}],"type":"visualization","typeMigrationVersion":"8.5.0","updated_at":"2024-01-24T18:53:06.818Z","version":"WzEzLDFd"} -{"attributes":{"description":"","layerListJSON":"[{\"locale\":\"autoselect\",\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"isAutoSelect\":true,\"lightModeDefault\":\"road_map_desaturated\"},\"id\":\"5ff9c98e-e0d3-4aff-ac98-b33c191496b4\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"alpha\":1,\"visible\":true,\"style\":{\"type\":\"EMS_VECTOR_TILE\",\"color\":\"\"},\"includeInFitToBounds\":true,\"type\":\"EMS_VECTOR_TILE\"}]","mapStateJSON":"{\"adHocDataViews\":[],\"zoom\":1.4,\"center\":{\"lon\":0,\"lat\":19.94277},\"timeFilters\":{\"from\":\"now-15m\",\"to\":\"now\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":60000},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"settings\":{\"autoFitToDataBounds\":false,\"backgroundColor\":\"#ffffff\",\"customIcons\":[],\"disableInteractive\":false,\"disableTooltipControl\":false,\"hideToolbarOverlay\":false,\"hideLayerControl\":false,\"hideViewControl\":false,\"initialLocation\":\"LAST_SAVED_LOCATION\",\"fixedLocation\":{\"lat\":0,\"lon\":0,\"zoom\":2},\"browserLocation\":{\"zoom\":2},\"keydownScrollZoom\":false,\"maxZoom\":24,\"minZoom\":0,\"showScaleControl\":false,\"showSpatialFilters\":true,\"showTimesliderToggleButton\":true,\"spatialFiltersAlpa\":0.3,\"spatialFiltersFillColor\":\"#DA8B45\",\"spatialFiltersLineColor\":\"#DA8B45\"}}","title":"Map","uiStateJSON":"{\"isLayerTOCOpen\":true,\"openTOCDetails\":[]}"},"coreMigrationVersion":"8.8.0","created_at":"2024-01-24T18:22:40.360Z","id":"managed-d7ab-46eb-a807-8fed28ed8566","managed":true,"references":[],"type":"map","typeMigrationVersion":"8.4.0","updated_at":"2024-01-24T18:23:07.090Z","version":"WzEyLDFd"} +{"attributes":{"description":"","layerListJSON":"[{\"locale\":\"autoselect\",\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"isAutoSelect\":true,\"lightModeDefault\":\"road_map_desaturated\"},\"id\":\"5ff9c98e-e0d3-4aff-ac98-b33c191496b4\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"alpha\":1,\"visible\":true,\"style\":{\"type\":\"EMS_VECTOR_TILE\",\"color\":\"\"},\"includeInFitToBounds\":true,\"type\":\"EMS_VECTOR_TILE\"}]","mapStateJSON":"{\"adHocDataViews\":[],\"zoom\":1.4,\"center\":{\"lon\":0,\"lat\":19.94277},\"timeFilters\":{\"from\":\"now-15m\",\"to\":\"now\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":60000},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"settings\":{\"autoFitToDataBounds\":false,\"backgroundColor\":\"#ffffff\",\"customIcons\":[],\"disableInteractive\":false,\"disableTooltipControl\":false,\"hideToolbarOverlay\":false,\"hideLayerControl\":false,\"hideViewControl\":false,\"initialLocation\":\"LAST_SAVED_LOCATION\",\"fixedLocation\":{\"lat\":0,\"lon\":0,\"zoom\":2},\"browserLocation\":{\"zoom\":2},\"keydownScrollZoom\":false,\"maxZoom\":24,\"minZoom\":0,\"showScaleControl\":false,\"showSpatialFilters\":true,\"showTimesliderToggleButton\":true,\"spatialFiltersAlpa\":0.3,\"spatialFiltersFillColor\":\"#DA8B45\",\"spatialFiltersLineColor\":\"#DA8B45\"}}","title":"Managed map","uiStateJSON":"{\"isLayerTOCOpen\":true,\"openTOCDetails\":[]}"},"coreMigrationVersion":"8.8.0","created_at":"2024-01-24T18:22:40.360Z","id":"managed-d7ab-46eb-a807-8fed28ed8566","managed":true,"references":[],"type":"map","typeMigrationVersion":"8.4.0","updated_at":"2024-01-24T18:23:07.090Z","version":"WzEyLDFd"} -{"attributes":{"description":"","layerListJSON":"[{\"locale\":\"autoselect\",\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"isAutoSelect\":true,\"lightModeDefault\":\"road_map_desaturated\"},\"id\":\"5ff9c98e-e0d3-4aff-ac98-b33c191496b4\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"alpha\":1,\"visible\":true,\"style\":{\"type\":\"EMS_VECTOR_TILE\",\"color\":\"\"},\"includeInFitToBounds\":true,\"type\":\"EMS_VECTOR_TILE\"}]","mapStateJSON":"{\"adHocDataViews\":[],\"zoom\":1.4,\"center\":{\"lon\":0,\"lat\":19.94277},\"timeFilters\":{\"from\":\"now-15m\",\"to\":\"now\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":60000},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"settings\":{\"autoFitToDataBounds\":false,\"backgroundColor\":\"#ffffff\",\"customIcons\":[],\"disableInteractive\":false,\"disableTooltipControl\":false,\"hideToolbarOverlay\":false,\"hideLayerControl\":false,\"hideViewControl\":false,\"initialLocation\":\"LAST_SAVED_LOCATION\",\"fixedLocation\":{\"lat\":0,\"lon\":0,\"zoom\":2},\"browserLocation\":{\"zoom\":2},\"keydownScrollZoom\":false,\"maxZoom\":24,\"minZoom\":0,\"showScaleControl\":false,\"showSpatialFilters\":true,\"showTimesliderToggleButton\":true,\"spatialFiltersAlpa\":0.3,\"spatialFiltersFillColor\":\"#DA8B45\",\"spatialFiltersLineColor\":\"#DA8B45\"}}","title":"Map","uiStateJSON":"{\"isLayerTOCOpen\":true,\"openTOCDetails\":[]}"},"coreMigrationVersion":"8.8.0","created_at":"2024-01-24T18:22:40.360Z","id":"unmanaged-d7ab-46eb-a807-8fed28ed8566","managed":false,"references":[],"type":"map","typeMigrationVersion":"8.4.0","updated_at":"2024-01-24T18:23:07.090Z","version":"WzEyLDFd"} +{"attributes":{"description":"","layerListJSON":"[{\"locale\":\"autoselect\",\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"isAutoSelect\":true,\"lightModeDefault\":\"road_map_desaturated\"},\"id\":\"5ff9c98e-e0d3-4aff-ac98-b33c191496b4\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"alpha\":1,\"visible\":true,\"style\":{\"type\":\"EMS_VECTOR_TILE\",\"color\":\"\"},\"includeInFitToBounds\":true,\"type\":\"EMS_VECTOR_TILE\"}]","mapStateJSON":"{\"adHocDataViews\":[],\"zoom\":1.4,\"center\":{\"lon\":0,\"lat\":19.94277},\"timeFilters\":{\"from\":\"now-15m\",\"to\":\"now\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":60000},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"settings\":{\"autoFitToDataBounds\":false,\"backgroundColor\":\"#ffffff\",\"customIcons\":[],\"disableInteractive\":false,\"disableTooltipControl\":false,\"hideToolbarOverlay\":false,\"hideLayerControl\":false,\"hideViewControl\":false,\"initialLocation\":\"LAST_SAVED_LOCATION\",\"fixedLocation\":{\"lat\":0,\"lon\":0,\"zoom\":2},\"browserLocation\":{\"zoom\":2},\"keydownScrollZoom\":false,\"maxZoom\":24,\"minZoom\":0,\"showScaleControl\":false,\"showSpatialFilters\":true,\"showTimesliderToggleButton\":true,\"spatialFiltersAlpa\":0.3,\"spatialFiltersFillColor\":\"#DA8B45\",\"spatialFiltersLineColor\":\"#DA8B45\"}}","title":"Unmanaged map","uiStateJSON":"{\"isLayerTOCOpen\":true,\"openTOCDetails\":[]}"},"coreMigrationVersion":"8.8.0","created_at":"2024-01-24T18:22:40.360Z","id":"unmanaged-d7ab-46eb-a807-8fed28ed8566","managed":false,"references":[],"type":"map","typeMigrationVersion":"8.4.0","updated_at":"2024-01-24T18:23:07.090Z","version":"WzEyLDFd"} + +{"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}"},"optionsJSON":"{\"useMargins\":true,\"syncColors\":false,\"syncCursor\":true,\"syncTooltips\":false,\"hidePanelTitles\":false}","panelsJSON":"[{\"type\":\"visualization\",\"gridData\":{\"x\":0,\"y\":0,\"w\":24,\"h\":15,\"i\":\"536c52e4-ecf1-4cde-9323-cc1c3de1fdd2\"},\"panelIndex\":\"536c52e4-ecf1-4cde-9323-cc1c3de1fdd2\",\"embeddableConfig\":{\"enhancements\":{}},\"panelRefName\":\"panel_536c52e4-ecf1-4cde-9323-cc1c3de1fdd2\"},{\"type\":\"lens\",\"gridData\":{\"x\":24,\"y\":0,\"w\":24,\"h\":15,\"i\":\"f19bd0df-03e7-4181-9adf-e3b3b4c97e19\"},\"panelIndex\":\"f19bd0df-03e7-4181-9adf-e3b3b4c97e19\",\"embeddableConfig\":{\"enhancements\":{}},\"panelRefName\":\"panel_f19bd0df-03e7-4181-9adf-e3b3b4c97e19\"},{\"type\":\"map\",\"gridData\":{\"x\":0,\"y\":15,\"w\":24,\"h\":15,\"i\":\"26f25c71-2b7c-4540-8c1c-c003b5657978\"},\"panelIndex\":\"26f25c71-2b7c-4540-8c1c-c003b5657978\",\"embeddableConfig\":{\"mapCenter\":{\"lat\":19.94277,\"lon\":0,\"zoom\":1.4},\"mapBuffer\":{\"minLon\":-180,\"minLat\":-66.51326,\"maxLon\":180,\"maxLat\":66.51326},\"isLayerTOCOpen\":true,\"openTOCDetails\":[],\"hiddenLayers\":[],\"enhancements\":{}},\"panelRefName\":\"panel_26f25c71-2b7c-4540-8c1c-c003b5657978\"},{\"type\":\"search\",\"gridData\":{\"x\":24,\"y\":15,\"w\":24,\"h\":15,\"i\":\"d7e33257-de57-40cb-9171-3dd739dd1875\"},\"panelIndex\":\"d7e33257-de57-40cb-9171-3dd739dd1875\",\"embeddableConfig\":{\"enhancements\":{}},\"panelRefName\":\"panel_d7e33257-de57-40cb-9171-3dd739dd1875\"}]","timeRestore":false,"title":"Managed dashboard with by-ref panels","version":1},"coreMigrationVersion":"8.8.0","created_at":"2024-01-31T21:57:49.102Z","id":"c44c86f9-b105-4a9c-9a24-449a58a827f3","managed":true,"references":[{"id":"managed-feb9-4ba6-9538-1b8f67fb4f57","name":"536c52e4-ecf1-4cde-9323-cc1c3de1fdd2:panel_536c52e4-ecf1-4cde-9323-cc1c3de1fdd2","type":"visualization"},{"id":"managed-36db-4a3b-a4ba-7a64ab8f130b","name":"f19bd0df-03e7-4181-9adf-e3b3b4c97e19:panel_f19bd0df-03e7-4181-9adf-e3b3b4c97e19","type":"lens"},{"id":"managed-d7ab-46eb-a807-8fed28ed8566","name":"26f25c71-2b7c-4540-8c1c-c003b5657978:panel_26f25c71-2b7c-4540-8c1c-c003b5657978","type":"map"},{"id":"managed-3d62-4113-ac7c-de2e20a68fbc","name":"d7e33257-de57-40cb-9171-3dd739dd1875:panel_d7e33257-de57-40cb-9171-3dd739dd1875","type":"search"}],"type":"dashboard","typeMigrationVersion":"8.9.0","updated_at":"2024-01-31T21:57:49.102Z","version":"WzEwOSwxXQ=="} diff --git a/test/functional/page_objects/settings_page.ts b/test/functional/page_objects/settings_page.ts index 5e8fddb626efd..3208fb782e272 100644 --- a/test/functional/page_objects/settings_page.ts +++ b/test/functional/page_objects/settings_page.ts @@ -238,6 +238,9 @@ export class SettingsPageObject extends FtrService { async clickEditIndexButton() { await this.testSubjects.click('editIndexPatternButton'); + await this.retry.waitFor('flyout', async () => { + return await this.testSubjects.exists('indexPatternEditorFlyout'); + }); } async clickDeletePattern() { diff --git a/test/functional/services/dashboard/add_panel.ts b/test/functional/services/dashboard/add_panel.ts index 65adf6dee5359..5b16c6af9ca20 100644 --- a/test/functional/services/dashboard/add_panel.ts +++ b/test/functional/services/dashboard/add_panel.ts @@ -230,22 +230,41 @@ export class DashboardAddPanelService extends FtrService { return this.addEmbeddable(vizName, 'Visualization'); } - async addEmbeddable(embeddableName: string, embeddableType: string) { + async addEmbeddable( + embeddableName: string, + embeddableType?: string, + closePanelWhenComplete: boolean = true + ) { this.log.debug( `DashboardAddPanel.addEmbeddable, name: ${embeddableName}, type: ${embeddableType}` ); await this.ensureAddPanelIsShowing(); - await this.savedObjectsFinder.toggleFilter(embeddableType); - await this.savedObjectsFinder.filterEmbeddableNames(`"${embeddableName.replace('-', ' ')}"`); + await this.savedObjectsFinder.filterEmbeddableNames( + `${embeddableType ? 'type:(' + embeddableType + ') ' : ''}"${embeddableName.replace( + '-', + ' ' + )}"` + ); await this.testSubjects.click(`savedObjectTitle${embeddableName.split(' ').join('-')}`); await this.testSubjects.exists('addObjectToDashboardSuccess'); - await this.closeAddPanel(); + if (closePanelWhenComplete) { + await this.closeAddPanel(); + } // close "Added successfully" toast await this.common.clearAllToasts(); return embeddableName; } + async addEmbeddables(embeddables: Array<{ name: string; type?: string }>) { + const addedEmbeddables: string[] = []; + for (const { name, type } of embeddables) { + addedEmbeddables.push(await this.addEmbeddable(name, type, false)); + } + await this.closeAddPanel(); + return addedEmbeddables; + } + async panelAddLinkExists(name: string) { this.log.debug(`DashboardAddPanel.panelAddLinkExists(${name})`); await this.ensureAddPanelIsShowing(); diff --git a/test/functional/services/dashboard/panel_settings.ts b/test/functional/services/dashboard/panel_settings.ts index 36bd01b1fad0d..11b817b176b6e 100644 --- a/test/functional/services/dashboard/panel_settings.ts +++ b/test/functional/services/dashboard/panel_settings.ts @@ -64,6 +64,10 @@ export function DashboardCustomizePanelProvider({ getService, getPageObject }: F ); } }); + + await retry.waitFor('superDatePickerToggleQuickMenuButton to be present', async () => { + return Boolean(await this.findDatePickerQuickMenuButton()); + }); } public async disableCustomTimeRange() { diff --git a/tsconfig.base.json b/tsconfig.base.json index 3c6be26411d88..bb1f7b7877026 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -642,6 +642,8 @@ "@kbn/data-view-field-editor-plugin/*": ["src/plugins/data_view_field_editor/*"], "@kbn/data-view-management-plugin": ["src/plugins/data_view_management"], "@kbn/data-view-management-plugin/*": ["src/plugins/data_view_management/*"], + "@kbn/data-view-utils": ["packages/kbn-data-view-utils"], + "@kbn/data-view-utils/*": ["packages/kbn-data-view-utils/*"], "@kbn/data-views-plugin": ["src/plugins/data_views"], "@kbn/data-views-plugin/*": ["src/plugins/data_views/*"], "@kbn/data-visualizer-plugin": ["x-pack/plugins/data_visualizer"], diff --git a/versions.json b/versions.json index 90d1a205d66d6..8a5f18231e404 100644 --- a/versions.json +++ b/versions.json @@ -8,13 +8,13 @@ "currentMinor": true }, { - "version": "8.12.1", + "version": "8.12.2", "branch": "8.12", "currentMajor": true, "previousMinor": true }, { - "version": "7.17.18", + "version": "7.17.19", "branch": "7.17", "previousMajor": true } diff --git a/x-pack/packages/kbn-alerting-state-types/src/lifecycle_state.ts b/x-pack/packages/kbn-alerting-state-types/src/lifecycle_state.ts index 8fa5cb111f6d9..f82791dd4e4ea 100644 --- a/x-pack/packages/kbn-alerting-state-types/src/lifecycle_state.ts +++ b/x-pack/packages/kbn-alerting-state-types/src/lifecycle_state.ts @@ -20,6 +20,8 @@ const trackedAlertStateRt = t.type({ // count of consecutive recovered alerts for flapping // will reset if the alert is active or if equal to the statusChangeThreshold stored in the rule settings pendingRecoveredCount: t.number, + // count of consecutive active alerts will reset if the alert is recovered + activeCount: t.number, }); export type TrackedLifecycleAlertState = t.TypeOf; diff --git a/x-pack/packages/kbn-alerting-state-types/src/task_state/v1/schema.ts b/x-pack/packages/kbn-alerting-state-types/src/task_state/v1/schema.ts index 247c0d7fc8d87..247cc7124c000 100644 --- a/x-pack/packages/kbn-alerting-state-types/src/task_state/v1/schema.ts +++ b/x-pack/packages/kbn-alerting-state-types/src/task_state/v1/schema.ts @@ -36,8 +36,7 @@ export const metaSchema = schema.object({ // will reset if the alert is active or if equal to the statusChangeThreshold stored in the rule settings pendingRecoveredCount: schema.maybe(schema.number()), uuid: schema.maybe(schema.string()), - // count of consecutive active alerts - // will reset if the alert is recovered or if equal to notificationDelay.active stored in the rule + // count of consecutive active alerts will reset if the alert is recovered activeCount: schema.maybe(schema.number()), }); diff --git a/x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts b/x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts index c939d25cfac15..58e294407735f 100644 --- a/x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts +++ b/x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts @@ -27,6 +27,7 @@ import { settingsSchema, sloIdSchema, summarySchema, + groupSummarySchema, tagsSchema, timeWindowSchema, timeWindowTypeSchema, @@ -101,6 +102,23 @@ const findSLOParamsSchema = t.partial({ }), }); +const groupBySchema = t.union([ + t.literal('ungrouped'), + t.literal('slo.tags'), + t.literal('status'), + t.literal('slo.indicator.type'), +]); + +const findSLOGroupsParamsSchema = t.partial({ + query: t.partial({ + page: t.string, + perPage: t.string, + groupBy: groupBySchema, + kqlQuery: t.string, + filters: t.string, + }), +}); + const sloResponseSchema = t.intersection([ t.type({ id: sloIdSchema, @@ -129,6 +147,12 @@ const sloWithSummaryResponseSchema = t.intersection([ t.type({ summary: summarySchema }), ]); +const sloGroupWithSummaryResponseSchema = t.type({ + group: t.string, + groupBy: t.string, + summary: groupSummarySchema, +}); + const getSLOQuerySchema = t.partial({ query: t.partial({ instanceId: allOrAnyString, @@ -181,6 +205,13 @@ const findSLOResponseSchema = t.type({ results: t.array(sloWithSummaryResponseSchema), }); +const findSLOGroupsResponseSchema = t.type({ + page: t.number, + perPage: t.number, + total: t.number, + results: t.array(sloGroupWithSummaryResponseSchema), +}); + const deleteSLOInstancesParamsSchema = t.type({ body: t.type({ list: t.array(t.type({ sloId: sloIdSchema, instanceId: t.string })) }), }); @@ -248,6 +279,8 @@ const getSLOInstancesResponseSchema = t.type({ type SLOResponse = t.OutputOf; type SLOWithSummaryResponse = t.OutputOf; +type SLOGroupWithSummaryResponse = t.OutputOf; + type CreateSLOInput = t.OutputOf; // Raw payload sent by the frontend type CreateSLOParams = t.TypeOf; // Parsed payload used by the backend type CreateSLOResponse = t.TypeOf; // Raw response sent to the frontend @@ -267,6 +300,9 @@ type UpdateSLOResponse = t.OutputOf; type FindSLOParams = t.TypeOf; type FindSLOResponse = t.OutputOf; +type FindSLOGroupsParams = t.TypeOf; +type FindSLOGroupsResponse = t.OutputOf; + type DeleteSLOInstancesInput = t.OutputOf; type DeleteSLOInstancesParams = t.TypeOf; @@ -297,6 +333,7 @@ type TimesliceMetricDocCountMetric = t.OutputOf; type HistogramIndicator = t.OutputOf; type KQLCustomIndicator = t.OutputOf; +type GroupSummary = t.TypeOf; export { createSLOParamsSchema, @@ -304,6 +341,8 @@ export { deleteSLOInstancesParamsSchema, findSLOParamsSchema, findSLOResponseSchema, + findSLOGroupsParamsSchema, + findSLOGroupsResponseSchema, getPreviewDataParamsSchema, getPreviewDataResponseSchema, getSLOParamsSchema, @@ -317,6 +356,7 @@ export { resetSLOResponseSchema, sloResponseSchema, sloWithSummaryResponseSchema, + sloGroupWithSummaryResponseSchema, updateSLOParamsSchema, updateSLOResponseSchema, getSLOBurnRatesParamsSchema, @@ -333,6 +373,8 @@ export type { DeleteSLOInstancesParams, FindSLOParams, FindSLOResponse, + FindSLOGroupsParams, + FindSLOGroupsResponse, GetPreviewDataParams, GetPreviewDataResponse, GetSLOParams, @@ -347,6 +389,7 @@ export type { ResetSLOResponse, SLOResponse, SLOWithSummaryResponse, + SLOGroupWithSummaryResponse, UpdateSLOInput, UpdateSLOParams, UpdateSLOResponse, @@ -365,4 +408,5 @@ export type { HistogramIndicator, KQLCustomIndicator, TimeWindow, + GroupSummary, }; diff --git a/x-pack/packages/kbn-slo-schema/src/schema/common.ts b/x-pack/packages/kbn-slo-schema/src/schema/common.ts index 55e597e759d65..b907ae8cda1c4 100644 --- a/x-pack/packages/kbn-slo-schema/src/schema/common.ts +++ b/x-pack/packages/kbn-slo-schema/src/schema/common.ts @@ -43,6 +43,23 @@ const summarySchema = t.type({ errorBudget: errorBudgetSchema, }); +const groupSummarySchema = t.type({ + total: t.number, + worst: t.type({ + sliValue: t.number, + status: t.string, + slo: t.type({ + id: t.string, + instanceId: t.string, + name: t.string, + }), + }), + violated: t.number, + healthy: t.number, + degrading: t.number, + noData: t.number, +}); + const historicalSummarySchema = t.intersection([ t.type({ date: dateType, @@ -76,4 +93,5 @@ export { previewDataSchema, statusSchema, summarySchema, + groupSummarySchema, }; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/index.tsx index 3de5aba8bcc59..6e1819d35537d 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/index.tsx @@ -44,7 +44,7 @@ import { useAddToNewCase } from '../../use_add_to_new_case'; import { useMappings } from '../../use_mappings'; import { useUnallowedValues } from '../../use_unallowed_values'; import { useDataQualityContext } from '../data_quality_context'; -import { getSizeInBytes, postResult } from '../../helpers'; +import { formatStorageResult, postStorageResult, getSizeInBytes } from '../../helpers'; const EMPTY_MARKDOWN_COMMENTS: string[] = []; @@ -249,6 +249,8 @@ const IndexPropertiesComponent: React.FC = ({ }) : EMPTY_MARKDOWN_COMMENTS; + const checkedAt = partitionedFieldMetadata ? Date.now() : undefined; + const updatedRollup = { ...patternRollup, results: { @@ -262,13 +264,14 @@ const IndexPropertiesComponent: React.FC = ({ markdownComments, pattern, sameFamily: indexSameFamily, + checkedAt, }, }, }; updatePatternRollup(updatedRollup); if (indexId && requestTime != null && requestTime > 0 && partitionedFieldMetadata) { - const checkMetadata = { + const report = { batchId: uuidv4(), ecsVersion: EcsVersion, errorCount: error ? 1 : 0, @@ -294,10 +297,13 @@ const IndexPropertiesComponent: React.FC = ({ partitionedFieldMetadata.incompatible ), }; - telemetryEvents.reportDataQualityIndexChecked?.(checkMetadata); + telemetryEvents.reportDataQualityIndexChecked?.(report); - const result = { meta: checkMetadata, rollup: updatedRollup }; - postResult({ result, httpFetch, toasts, abortController: new AbortController() }); + const result = updatedRollup.results[indexName]; + if (result) { + const storageResult = formatStorageResult({ result, report, partitionedFieldMetadata }); + postStorageResult({ storageResult, httpFetch, toasts }); + } } } } diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/helpers.test.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/helpers.test.ts index b2a7f6ef26a6e..cdf6251113751 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/helpers.test.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/helpers.test.ts @@ -274,6 +274,7 @@ describe('helpers', () => { ], pattern: 'auditbeat-*', sameFamily: 0, + checkedAt: 1706526408000, }, }; const isILMAvailable = true; @@ -300,6 +301,7 @@ describe('helpers', () => { pattern: 'auditbeat-*', patternDocsCount: 4, sizeInBytes: 733175040, + checkedAt: undefined, }, { docsCount: 1628343, @@ -309,6 +311,7 @@ describe('helpers', () => { pattern: 'auditbeat-*', patternDocsCount: 4, sizeInBytes: 731583142, + checkedAt: undefined, }, { docsCount: 4, @@ -318,6 +321,7 @@ describe('helpers', () => { pattern: 'auditbeat-*', patternDocsCount: 4, sizeInBytes: 28413, + checkedAt: 1706526408000, }, ]); }); @@ -344,6 +348,7 @@ describe('helpers', () => { pattern: 'auditbeat-*', patternDocsCount: 4, sizeInBytes: 733175040, + checkedAt: undefined, }, { docsCount: 1628343, @@ -353,6 +358,7 @@ describe('helpers', () => { pattern: 'auditbeat-*', patternDocsCount: 4, sizeInBytes: 731583142, + checkedAt: undefined, }, { docsCount: 4, @@ -362,6 +368,7 @@ describe('helpers', () => { pattern: 'auditbeat-*', patternDocsCount: 4, sizeInBytes: 28413, + checkedAt: 1706526408000, }, ]); }); @@ -388,6 +395,7 @@ describe('helpers', () => { pattern: 'auditbeat-*', patternDocsCount: 4, sizeInBytes: 28413, + checkedAt: 1706526408000, }, { docsCount: 1628343, @@ -397,6 +405,7 @@ describe('helpers', () => { pattern: 'auditbeat-*', patternDocsCount: 4, sizeInBytes: 731583142, + checkedAt: undefined, }, { docsCount: 1630289, @@ -406,6 +415,7 @@ describe('helpers', () => { pattern: 'auditbeat-*', patternDocsCount: 4, sizeInBytes: 733175040, + checkedAt: undefined, }, ]); }); @@ -432,6 +442,7 @@ describe('helpers', () => { pattern: 'auditbeat-*', patternDocsCount: 4, sizeInBytes: 0, + checkedAt: undefined, }, { docsCount: 0, @@ -441,6 +452,7 @@ describe('helpers', () => { pattern: 'auditbeat-*', patternDocsCount: 4, sizeInBytes: 0, + checkedAt: undefined, }, { docsCount: 0, @@ -450,6 +462,7 @@ describe('helpers', () => { pattern: 'auditbeat-*', patternDocsCount: 4, sizeInBytes: 0, + checkedAt: undefined, }, ]); }); @@ -701,6 +714,7 @@ describe('helpers', () => { pattern: 'auditbeat-*', patternDocsCount: 1118155, sizeInBytes: 43357342, + checkedAt: 1706526408000, }, { docsCount: 48068, @@ -710,6 +724,7 @@ describe('helpers', () => { pattern: 'auditbeat-*', patternDocsCount: 1118155, sizeInBytes: 32460397, + checkedAt: 1706526408000, }, { docsCount: 48064, @@ -719,6 +734,7 @@ describe('helpers', () => { pattern: 'auditbeat-*', patternDocsCount: 1118155, sizeInBytes: 42782794, + checkedAt: 1706526408000, }, { docsCount: 47868, @@ -728,6 +744,7 @@ describe('helpers', () => { pattern: 'auditbeat-*', patternDocsCount: 1118155, sizeInBytes: 31575964, + checkedAt: 1706526408000, }, { docsCount: 47827, @@ -737,6 +754,7 @@ describe('helpers', () => { pattern: 'auditbeat-*', patternDocsCount: 1118155, sizeInBytes: 44130657, + checkedAt: 1706526408000, }, { docsCount: 47642, @@ -746,6 +764,7 @@ describe('helpers', () => { pattern: 'auditbeat-*', patternDocsCount: 1118155, sizeInBytes: 42412521, + checkedAt: 1706526408000, }, { docsCount: 47545, @@ -755,6 +774,7 @@ describe('helpers', () => { pattern: 'auditbeat-*', patternDocsCount: 1118155, sizeInBytes: 41423244, + checkedAt: 1706526408000, }, { docsCount: 47531, @@ -764,6 +784,7 @@ describe('helpers', () => { pattern: 'auditbeat-*', patternDocsCount: 1118155, sizeInBytes: 32394133, + checkedAt: 1706526408000, }, { docsCount: 47530, @@ -773,6 +794,7 @@ describe('helpers', () => { pattern: 'auditbeat-*', patternDocsCount: 1118155, sizeInBytes: 43015519, + checkedAt: 1706526408000, }, { docsCount: 47520, @@ -782,6 +804,7 @@ describe('helpers', () => { pattern: 'auditbeat-*', patternDocsCount: 1118155, sizeInBytes: 42230604, + checkedAt: 1706526408000, }, { docsCount: 47496, @@ -791,6 +814,7 @@ describe('helpers', () => { pattern: 'auditbeat-*', patternDocsCount: 1118155, sizeInBytes: 41710968, + checkedAt: 1706526408000, }, { docsCount: 47486, @@ -800,6 +824,7 @@ describe('helpers', () => { pattern: 'auditbeat-*', patternDocsCount: 1118155, sizeInBytes: 42295944, + checkedAt: 1706526408000, }, { docsCount: 47486, @@ -809,6 +834,7 @@ describe('helpers', () => { pattern: 'auditbeat-*', patternDocsCount: 1118155, sizeInBytes: 41761321, + checkedAt: 1706526408000, }, { docsCount: 47460, @@ -818,6 +844,7 @@ describe('helpers', () => { pattern: 'auditbeat-*', patternDocsCount: 1118155, sizeInBytes: 30481198, + checkedAt: 1706526408000, }, { docsCount: 47439, @@ -827,6 +854,7 @@ describe('helpers', () => { pattern: 'auditbeat-*', patternDocsCount: 1118155, sizeInBytes: 41554041, + checkedAt: 1706526408000, }, { docsCount: 47395, @@ -836,6 +864,7 @@ describe('helpers', () => { pattern: 'auditbeat-*', patternDocsCount: 1118155, sizeInBytes: 42815907, + checkedAt: 1706526408000, }, { docsCount: 47394, @@ -845,6 +874,7 @@ describe('helpers', () => { pattern: 'auditbeat-*', patternDocsCount: 1118155, sizeInBytes: 41157112, + checkedAt: 1706526408000, }, { docsCount: 47372, @@ -854,6 +884,7 @@ describe('helpers', () => { pattern: 'auditbeat-*', patternDocsCount: 1118155, sizeInBytes: 31626792, + checkedAt: 1706526408000, }, { docsCount: 47369, @@ -863,6 +894,7 @@ describe('helpers', () => { pattern: 'auditbeat-*', patternDocsCount: 1118155, sizeInBytes: 41828969, + checkedAt: 1706526408000, }, { docsCount: 47348, @@ -872,6 +904,7 @@ describe('helpers', () => { pattern: 'auditbeat-*', patternDocsCount: 1118155, sizeInBytes: 40010773, + checkedAt: 1706526408000, }, { docsCount: 47339, @@ -881,6 +914,7 @@ describe('helpers', () => { pattern: 'auditbeat-*', patternDocsCount: 1118155, sizeInBytes: 43480570, + checkedAt: 1706526408000, }, { docsCount: 47325, @@ -890,6 +924,7 @@ describe('helpers', () => { pattern: 'auditbeat-*', patternDocsCount: 1118155, sizeInBytes: 41822475, + checkedAt: 1706526408000, }, { docsCount: 47294, @@ -899,6 +934,7 @@ describe('helpers', () => { pattern: 'auditbeat-*', patternDocsCount: 1118155, sizeInBytes: 43018490, + checkedAt: 1706526408000, }, { docsCount: 24276, @@ -908,6 +944,7 @@ describe('helpers', () => { pattern: 'auditbeat-*', patternDocsCount: 1118155, sizeInBytes: 23579440, + checkedAt: 1706526408000, }, { docsCount: 4, @@ -917,6 +954,7 @@ describe('helpers', () => { pattern: 'auditbeat-*', patternDocsCount: 1118155, sizeInBytes: 28409, + checkedAt: 1706526408000, }, { docsCount: 0, @@ -926,6 +964,7 @@ describe('helpers', () => { pattern: 'auditbeat-*', patternDocsCount: 1118155, sizeInBytes: 247, + checkedAt: 1706526408000, }, ], pageSize: 10, diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/helpers.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/helpers.ts index 82fd312a947e5..1bddcd83e7a14 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/helpers.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/helpers.ts @@ -172,6 +172,7 @@ export const getSummaryTableItems = ({ pattern, patternDocsCount, sizeInBytes: getSizeInBytes({ stats, indexName }), + checkedAt: results?.[indexName]?.checkedAt, })); return orderBy([sortByColumn], [sortByDirection], summaryTableItems); diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/summary_table/helpers.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/summary_table/helpers.test.tsx index 6ab874eeac5b6..cfcd23b86d2a1 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/summary_table/helpers.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/summary_table/helpers.test.tsx @@ -143,6 +143,7 @@ describe('helpers', () => { pattern: 'auditbeat-*', patternDocsCount: 57410, sizeInBytes: 103344068, + checkedAt: Date.now(), }; const hasIncompatible: IndexSummaryTableItem = { @@ -188,6 +189,7 @@ describe('helpers', () => { }, { field: 'ilmPhase', name: 'ILM Phase', sortable: true, truncateText: false }, { field: 'sizeInBytes', name: 'Size', sortable: true, truncateText: false }, + { field: 'checkedAt', name: 'Last check', sortable: true, truncateText: false }, ]); }); diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/summary_table/helpers.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/summary_table/helpers.tsx index f80678fff8cb2..55e26200f0e44 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/summary_table/helpers.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/summary_table/helpers.tsx @@ -5,8 +5,9 @@ * 2.0. */ -import type { EuiBasicTableColumn } from '@elastic/eui'; import { + EuiBasicTableColumn, + EuiText, EuiBadge, EuiButtonIcon, EuiIcon, @@ -17,6 +18,7 @@ import { RIGHT_ALIGNMENT, } from '@elastic/eui'; import React from 'react'; +import moment from 'moment'; import styled from 'styled-components'; import { EMPTY_STAT, getIlmPhaseDescription, getIncompatibleStatColor } from '../../helpers'; @@ -41,6 +43,7 @@ export interface IndexSummaryTableItem { pattern: string; patternDocsCount: number; sizeInBytes: number; + checkedAt: number | undefined; } export const getResultToolTip = (incompatible: number | undefined): string => { @@ -237,6 +240,17 @@ export const getSummaryTableColumns = ({ sortable: true, truncateText: false, }, + { + field: 'checkedAt', + name: i18n.LAST_CHECK, + render: (_, { checkedAt }) => ( + + {checkedAt && moment(checkedAt).isValid() ? moment(checkedAt).fromNow() : EMPTY_STAT} + + ), + sortable: true, + truncateText: false, + }, ]; export const getShowPagination = ({ diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/summary_table/translations.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/summary_table/translations.ts index 0101708db9f9d..8d8e4adb9944f 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/summary_table/translations.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/summary_table/translations.ts @@ -111,6 +111,13 @@ export const SIZE = i18n.translate( } ); +export const LAST_CHECK = i18n.translate( + 'securitySolutionPackages.ecsDataQualityDashboard.summaryTable.lastCheckColumn', + { + defaultMessage: 'Last check', + } +); + export const THIS_INDEX_HAS_NOT_BEEN_CHECKED = i18n.translate( 'securitySolutionPackages.ecsDataQualityDashboard.summaryTable.thisIndexHasNotBeenCheckedTooltip', { diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/helpers.test.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/helpers.test.ts index a20c8144c5773..830d463f5fe57 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/helpers.test.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/helpers.test.ts @@ -35,9 +35,9 @@ import { getTotalSizeInBytes, hasValidTimestampMapping, isMappingCompatible, - postResult, - getResults, - ResultData, + postStorageResult, + getStorageResults, + StorageResult, } from './helpers'; import { hostNameWithTextMapping, @@ -102,6 +102,7 @@ describe('helpers', () => { ], pattern: 'auditbeat-*', sameFamily: 0, + checkedAt: Date.now(), }; it('returns undefined when results is undefined', () => { @@ -1193,6 +1194,7 @@ describe('helpers', () => { markdownComments: ['foo', 'bar', 'baz'], pattern: 'packetbeat-*', sameFamily: 0, + checkedAt: Date.now(), }, '.ds-packetbeat-8.6.1-2023.02.04-000001': { docsCount: 1628343, @@ -1203,6 +1205,7 @@ describe('helpers', () => { markdownComments: ['foo', 'bar', 'baz'], pattern: 'packetbeat-*', sameFamily: 0, + checkedAt: Date.now(), }, }; @@ -1220,6 +1223,7 @@ describe('helpers', () => { markdownComments: ['foo', 'bar', 'baz'], pattern: 'auditbeat-*', sameFamily: 0, + checkedAt: Date.now(), }, 'auditbeat-custom-index-1': { docsCount: 4, @@ -1230,6 +1234,7 @@ describe('helpers', () => { markdownComments: ['foo', 'bar', 'baz'], pattern: 'auditbeat-*', sameFamily: 0, + checkedAt: Date.now(), }, 'auditbeat-custom-empty-index-1': { docsCount: 0, @@ -1240,6 +1245,7 @@ describe('helpers', () => { markdownComments: ['foo', 'bar', 'baz'], pattern: 'auditbeat-*', sameFamily: 0, + checkedAt: Date.now(), }, }; @@ -1257,6 +1263,7 @@ describe('helpers', () => { markdownComments: ['foo', 'bar', 'baz'], pattern: 'auditbeat-*', sameFamily: 0, + checkedAt: Date.now(), }, 'auditbeat-custom-index-1': { docsCount: 4, @@ -1267,6 +1274,7 @@ describe('helpers', () => { markdownComments: ['foo', 'bar', 'baz'], pattern: 'auditbeat-*', sameFamily: 0, + checkedAt: Date.now(), }, 'auditbeat-custom-empty-index-1': { docsCount: 0, @@ -1277,6 +1285,7 @@ describe('helpers', () => { markdownComments: ['foo', 'bar', 'baz'], pattern: 'auditbeat-*', sameFamily: 0, + checkedAt: Date.now(), }, }; @@ -1342,6 +1351,7 @@ describe('helpers', () => { markdownComments: ['foo', 'bar', 'baz'], pattern: 'packetbeat-*', sameFamily: 0, + checkedAt: Date.now(), }; expect(getErrorSummary(resultWithError)).toEqual({ @@ -1495,7 +1505,7 @@ describe('helpers', () => { }); }); - describe('postResult', () => { + describe('postStorageResult', () => { const { fetch } = httpServiceMock.createStartContract(); const { toasts } = notificationServiceMock.createStartContract(); beforeEach(() => { @@ -1503,10 +1513,10 @@ describe('helpers', () => { }); test('it posts the result', async () => { - const result = { meta: {}, rollup: {} } as unknown as ResultData; - await postResult({ + const storageResult = { indexName: 'test' } as unknown as StorageResult; + await postStorageResult({ + storageResult, httpFetch: fetch, - result, abortController: new AbortController(), toasts, }); @@ -1515,17 +1525,17 @@ describe('helpers', () => { '/internal/ecs_data_quality_dashboard/results', expect.objectContaining({ method: 'POST', - body: JSON.stringify(result), + body: JSON.stringify(storageResult), }) ); }); test('it throws error', async () => { - const result = { meta: {}, rollup: {} } as unknown as ResultData; + const storageResult = { indexName: 'test' } as unknown as StorageResult; fetch.mockRejectedValueOnce('test-error'); - await postResult({ + await postStorageResult({ httpFetch: fetch, - result, + storageResult, abortController: new AbortController(), toasts, }); @@ -1533,7 +1543,7 @@ describe('helpers', () => { }); }); - describe('getResults', () => { + describe('getStorageResults', () => { const { fetch } = httpServiceMock.createStartContract(); const { toasts } = notificationServiceMock.createStartContract(); beforeEach(() => { @@ -1541,10 +1551,10 @@ describe('helpers', () => { }); test('it gets the results', async () => { - await getResults({ + await getStorageResults({ httpFetch: fetch, abortController: new AbortController(), - patterns: ['auditbeat-*', 'packetbeat-*'], + pattern: 'auditbeat-*', toasts, }); @@ -1552,7 +1562,7 @@ describe('helpers', () => { '/internal/ecs_data_quality_dashboard/results', expect.objectContaining({ method: 'GET', - query: { patterns: 'auditbeat-*,packetbeat-*' }, + query: { pattern: 'auditbeat-*' }, }) ); }); @@ -1560,10 +1570,10 @@ describe('helpers', () => { it('should catch error', async () => { fetch.mockRejectedValueOnce('test-error'); - const results = await getResults({ + const results = await getStorageResults({ httpFetch: fetch, abortController: new AbortController(), - patterns: ['auditbeat-*', 'packetbeat-*'], + pattern: 'auditbeat-*', toasts, }); diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/helpers.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/helpers.ts index a4d51233232e4..2107e7d3949da 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/helpers.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/helpers.ts @@ -23,6 +23,7 @@ import type { EcsMetadata, EnrichedFieldMetadata, ErrorSummary, + IlmPhase, PartitionedFieldMetadata, PartitionedFieldMetadataStats, PatternRollup, @@ -449,51 +450,116 @@ export const getErrorSummaries = ( export const RESULTS_API_ROUTE = '/internal/ecs_data_quality_dashboard/results'; -export interface ResultData { - meta: DataQualityIndexCheckedParams; - rollup: PatternRollup; +export interface StorageResult { + batchId: string; + indexName: string; + isCheckAll: boolean; + checkedAt: number; + docsCount: number; + totalFieldCount: number; + ecsFieldCount: number; + customFieldCount: number; + incompatibleFieldCount: number; + sameFamilyFieldCount: number; + sameFamilyFields: string[]; + unallowedMappingFields: string[]; + unallowedValueFields: string[]; + sizeInBytes: number; + ilmPhase?: IlmPhase; + markdownComments: string[]; + ecsVersion: string; + indexId: string; + error: string | null; } -export async function postResult({ +export const formatStorageResult = ({ result, + report, + partitionedFieldMetadata, +}: { + result: DataQualityCheckResult; + report: DataQualityIndexCheckedParams; + partitionedFieldMetadata: PartitionedFieldMetadata; +}): StorageResult => ({ + batchId: report.batchId, + indexName: result.indexName, + isCheckAll: report.isCheckAll, + checkedAt: result.checkedAt ?? Date.now(), + docsCount: result.docsCount ?? 0, + totalFieldCount: partitionedFieldMetadata.all.length, + ecsFieldCount: partitionedFieldMetadata.ecsCompliant.length, + customFieldCount: partitionedFieldMetadata.custom.length, + incompatibleFieldCount: partitionedFieldMetadata.incompatible.length, + sameFamilyFieldCount: partitionedFieldMetadata.sameFamily.length, + sameFamilyFields: report.sameFamilyFields ?? [], + unallowedMappingFields: report.unallowedMappingFields ?? [], + unallowedValueFields: report.unallowedValueFields ?? [], + sizeInBytes: report.sizeInBytes ?? 0, + ilmPhase: result.ilmPhase, + markdownComments: result.markdownComments, + ecsVersion: report.ecsVersion, + indexId: report.indexId, + error: result.error, +}); + +export const formatResultFromStorage = ({ + storageResult, + pattern, +}: { + storageResult: StorageResult; + pattern: string; +}): DataQualityCheckResult => ({ + docsCount: storageResult.docsCount, + error: storageResult.error, + ilmPhase: storageResult.ilmPhase, + incompatible: storageResult.incompatibleFieldCount, + indexName: storageResult.indexName, + markdownComments: storageResult.markdownComments, + sameFamily: storageResult.sameFamilyFieldCount, + checkedAt: storageResult.checkedAt, + pattern, +}); + +export async function postStorageResult({ + storageResult, httpFetch, toasts, - abortController, + abortController = new AbortController(), }: { - result: ResultData; + storageResult: StorageResult; httpFetch: HttpHandler; toasts: IToasts; - abortController: AbortController; + abortController?: AbortController; }): Promise { try { await httpFetch(RESULTS_API_ROUTE, { method: 'POST', signal: abortController.signal, version: INTERNAL_API_VERSION, - body: JSON.stringify(result), + body: JSON.stringify(storageResult), }); } catch (err) { toasts.addError(err, { title: i18n.POST_RESULT_ERROR_TITLE }); } } -export async function getResults({ - patterns, +export async function getStorageResults({ + pattern, httpFetch, toasts, abortController, }: { - patterns: string[]; + pattern: string; httpFetch: HttpHandler; toasts: IToasts; abortController: AbortController; -}): Promise { +}): Promise { try { - const results = await httpFetch(RESULTS_API_ROUTE, { + const results = await httpFetch(RESULTS_API_ROUTE, { method: 'GET', signal: abortController.signal, version: INTERNAL_API_VERSION, - query: { patterns: patterns.join(',') }, + query: { pattern }, }); return results; } catch (err) { diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/data_quality_check_result/mock_index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/data_quality_check_result/mock_index.tsx index f179a82c232e9..7f61f91b87212 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/data_quality_check_result/mock_index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/data_quality_check_result/mock_index.tsx @@ -23,6 +23,7 @@ export const mockDataQualityCheckResult: Record ], pattern: 'auditbeat-*', sameFamily: 0, + checkedAt: 1706526408000, }, 'auditbeat-7.9.3-2023.02.13-000001': { docsCount: 2438, @@ -39,5 +40,6 @@ export const mockDataQualityCheckResult: Record ], pattern: 'auditbeat-*', sameFamily: 0, + checkedAt: 1706526408000, }, }; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/pattern_rollup/mock_alerts_pattern_rollup.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/pattern_rollup/mock_alerts_pattern_rollup.ts index f121621ff1ac1..513d034f52263 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/pattern_rollup/mock_alerts_pattern_rollup.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/pattern_rollup/mock_alerts_pattern_rollup.ts @@ -83,6 +83,7 @@ export const alertIndexWithAllResults: PatternRollup = { markdownComments: ['foo', 'bar', 'baz'], pattern: '.alerts-security.alerts-default', sameFamily: 0, + checkedAt: 1706526408000, }, }, sizeInBytes: 29717961631, diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/pattern_rollup/mock_auditbeat_pattern_rollup.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/pattern_rollup/mock_auditbeat_pattern_rollup.ts index 2f83f899dc0d2..15bb39a01714f 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/pattern_rollup/mock_auditbeat_pattern_rollup.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/pattern_rollup/mock_auditbeat_pattern_rollup.ts @@ -155,6 +155,7 @@ export const auditbeatWithAllResults: PatternRollup = { markdownComments: ['foo', 'bar', 'baz'], pattern: 'auditbeat-*', sameFamily: 0, + checkedAt: 1706526408000, }, 'auditbeat-custom-index-1': { docsCount: 4, @@ -165,6 +166,7 @@ export const auditbeatWithAllResults: PatternRollup = { markdownComments: ['foo', 'bar', 'baz'], pattern: 'auditbeat-*', sameFamily: 0, + checkedAt: 1706526408000, }, 'auditbeat-custom-empty-index-1': { docsCount: 0, @@ -175,6 +177,7 @@ export const auditbeatWithAllResults: PatternRollup = { markdownComments: ['foo', 'bar', 'baz'], pattern: 'auditbeat-*', sameFamily: 0, + checkedAt: 1706526408000, }, }, sizeInBytes: 18820446, diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/pattern_rollup/mock_packetbeat_pattern_rollup.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/pattern_rollup/mock_packetbeat_pattern_rollup.ts index 369803a44a3dd..339f1993e292d 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/pattern_rollup/mock_packetbeat_pattern_rollup.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/pattern_rollup/mock_packetbeat_pattern_rollup.ts @@ -132,6 +132,7 @@ export const packetbeatWithSomeErrors: PatternRollup = { markdownComments: ['foo', 'bar', 'baz'], pattern: 'packetbeat-*', sameFamily: undefined, + checkedAt: 1706526408000, }, '.ds-packetbeat-8.6.1-2023.02.04-000001': { docsCount: 1628343, @@ -142,6 +143,7 @@ export const packetbeatWithSomeErrors: PatternRollup = { markdownComments: ['foo', 'bar', 'baz'], pattern: 'packetbeat-*', sameFamily: 0, + checkedAt: 1706526408000, }, }, sizeInBytes: 1096520898, diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/types.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/types.ts index 9f507992d1509..f462777e1fc0b 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/types.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/types.ts @@ -107,6 +107,7 @@ export interface DataQualityCheckResult { markdownComments: string[]; sameFamily: number | undefined; pattern: string; + checkedAt: number | undefined; } export interface PatternRollup { @@ -186,8 +187,8 @@ export type DataQualityIndexCheckedParams = DataQualityCheckAllCompletedParams & export interface DataQualityCheckAllCompletedParams { batchId: string; - ecsVersion?: string; - isCheckAll?: boolean; + ecsVersion: string; + isCheckAll: boolean; numberOfDocuments?: number; numberOfIncompatibleFields?: number; numberOfIndices?: number; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_results_rollup/helpers.test.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_results_rollup/helpers.test.ts index b37d0aecc25cf..b948b9584f996 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_results_rollup/helpers.test.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_results_rollup/helpers.test.ts @@ -61,6 +61,7 @@ describe('helpers', () => { markdownComments: ['foo', 'bar', 'baz'], pattern: '.alerts-security.alerts-default', sameFamily: 7, + checkedAt: 1706526408000, }; const alertIndexWithSameFamily: PatternRollup = { @@ -260,6 +261,7 @@ describe('helpers', () => { ], pattern: 'packetbeat-*', sameFamily: 0, + checkedAt: expect.any(Number), }, }, sizeInBytes: 1464758182, @@ -361,6 +363,7 @@ describe('helpers', () => { ], pattern: 'packetbeat-*', sameFamily: 0, + checkedAt: expect.any(Number), }, }, sizeInBytes: 1464758182, @@ -450,6 +453,7 @@ describe('helpers', () => { indexName: '.ds-packetbeat-8.6.1-2023.02.04-000001', markdownComments: [], pattern: 'packetbeat-*', + checkedAt: undefined, }, }, sizeInBytes: 1464758182, @@ -510,6 +514,7 @@ describe('helpers', () => { ], pattern: 'packetbeat-*', sameFamily: 0, + checkedAt: expect.any(Number), }, }, sizeInBytes: 1464758182, diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_results_rollup/helpers.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_results_rollup/helpers.ts index cca8ac331aa88..07f51572b6ba2 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_results_rollup/helpers.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_results_rollup/helpers.ts @@ -140,6 +140,7 @@ export const updateResultOnCheckCompleted = ({ const incompatible = partitionedFieldMetadata?.incompatible.length; const sameFamily = partitionedFieldMetadata?.sameFamily.length; + const checkedAt = partitionedFieldMetadata ? Date.now() : undefined; return { ...patternRollups, @@ -156,6 +157,7 @@ export const updateResultOnCheckCompleted = ({ markdownComments, pattern, sameFamily, + checkedAt, }, }, }, diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_results_rollup/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_results_rollup/index.tsx index 27810fedffde4..907ffca5a45f6 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_results_rollup/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_results_rollup/index.tsx @@ -19,14 +19,16 @@ import { updateResultOnCheckCompleted, } from './helpers'; -import type { OnCheckCompleted, PatternRollup } from '../types'; +import type { DataQualityCheckResult, OnCheckCompleted, PatternRollup } from '../types'; import { getDocsCount, getIndexId, - getResults, + getStorageResults, getSizeInBytes, getTotalPatternSameFamily, - postResult, + postStorageResult, + formatStorageResult, + formatResultFromStorage, } from '../helpers'; import { getIlmPhase, getIndexIncompatible } from '../data_quality_panel/pattern/helpers'; import { useDataQualityContext } from '../data_quality_panel/data_quality_context'; @@ -60,9 +62,11 @@ interface UseResultsRollup { updatePatternRollup: (patternRollup: PatternRollup) => void; } -const useStoredPatternRollups = (patterns: string[]) => { +const useStoredPatternResults = (patterns: string[]) => { const { httpFetch, toasts } = useDataQualityContext(); - const [storedRollups, setStoredRollups] = useState>({}); + const [storedPatternResults, setStoredPatternResults] = useState< + Array<{ pattern: string; results: Record }> + >([]); useEffect(() => { if (isEmpty(patterns)) { @@ -71,20 +75,31 @@ const useStoredPatternRollups = (patterns: string[]) => { let ignore = false; const abortController = new AbortController(); - const fetchStoredRollups = async () => { - const results = await getResults({ httpFetch, abortController, patterns, toasts }); - if (results?.length && !ignore) { - setStoredRollups(Object.fromEntries(results.map(({ rollup }) => [rollup.pattern, rollup]))); + const fetchStoredPatternResults = async () => { + const requests = patterns.map((pattern) => + getStorageResults({ pattern, httpFetch, abortController, toasts }).then((results = []) => ({ + pattern, + results: Object.fromEntries( + results.map((storageResult) => [ + storageResult.indexName, + formatResultFromStorage({ storageResult, pattern }), + ]) + ), + })) + ); + const patternResults = await Promise.all(requests); + if (patternResults?.length && !ignore) { + setStoredPatternResults(patternResults); } }; - fetchStoredRollups(); + fetchStoredPatternResults(); return () => { ignore = true; }; }, [httpFetch, patterns, toasts]); - return storedRollups; + return storedPatternResults; }; export const useResultsRollup = ({ ilmPhases, patterns }: Props): UseResultsRollup => { @@ -92,28 +107,36 @@ export const useResultsRollup = ({ ilmPhases, patterns }: Props): UseResultsRoll const [patternIndexNames, setPatternIndexNames] = useState>({}); const [patternRollups, setPatternRollups] = useState>({}); - const storedPatternsRollups = useStoredPatternRollups(patterns); + const storedPatternsResults = useStoredPatternResults(patterns); useEffect(() => { - if (!isEmpty(storedPatternsRollups)) { - setPatternRollups((current) => ({ ...current, ...storedPatternsRollups })); + if (!isEmpty(storedPatternsResults)) { + setPatternRollups((current) => + storedPatternsResults.reduce( + (acc, { pattern, results }) => ({ + ...acc, + [pattern]: { + ...current[pattern], + pattern, + results, + }, + }), + current + ) + ); } - }, [storedPatternsRollups]); - - const updatePatternRollups = useCallback( - (updateRollups: (current: Record) => Record) => { - setPatternRollups((current) => updateRollups(current)); - }, - [] - ); + }, [storedPatternsResults]); const { telemetryEvents, isILMAvailable } = useDataQualityContext(); - const updatePatternRollup = useCallback( - (patternRollup: PatternRollup) => { - updatePatternRollups((current) => ({ ...current, [patternRollup.pattern]: patternRollup })); - }, - [updatePatternRollups] - ); + const updatePatternRollup = useCallback((patternRollup: PatternRollup) => { + setPatternRollups((current) => ({ + ...current, + [patternRollup.pattern]: { + ...patternRollup, + results: patternRollup.results ?? current[patternRollup.pattern]?.results, // prevent undefined results override existing + }, + })); + }, []); const totalDocsCount = useMemo(() => getTotalDocsCount(patternRollups), [patternRollups]); const totalIncompatible = useMemo(() => getTotalIncompatible(patternRollups), [patternRollups]); @@ -170,7 +193,7 @@ export const useResultsRollup = ({ ilmPhases, patterns }: Props): UseResultsRoll requestTime > 0 && partitionedFieldMetadata ) { - const metadata = { + const report = { batchId, ecsVersion: EcsVersion, errorCount: error ? 1 : 0, @@ -199,10 +222,13 @@ export const useResultsRollup = ({ ilmPhases, patterns }: Props): UseResultsRoll partitionedFieldMetadata.incompatible ), }; - telemetryEvents.reportDataQualityIndexChecked?.(metadata); + telemetryEvents.reportDataQualityIndexChecked?.(report); - const result = { meta: metadata, rollup: updatedRollup }; - postResult({ result, httpFetch, toasts, abortController: new AbortController() }); + const result = results[indexName]; + if (result) { + const storageResult = formatStorageResult({ result, report, partitionedFieldMetadata }); + postStorageResult({ storageResult, httpFetch, toasts }); + } } if (isLastCheck) { diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/kibana.jsonc b/x-pack/packages/security-solution/ecs_data_quality_dashboard/kibana.jsonc index a001420fade88..30cc9cf249820 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/kibana.jsonc +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/kibana.jsonc @@ -1,5 +1,5 @@ { "type": "shared-browser", "id": "@kbn/ecs-data-quality-dashboard", - "owner": "@elastic/security-threat-hunting-investigations" + "owner": "@elastic/security-threat-hunting-explore" } diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_content.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_content.tsx index daac98f67f750..1ed80f54be2a6 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_content.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_content.tsx @@ -15,7 +15,6 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import type { DataView } from '@kbn/data-views-plugin/public'; -import type { Dictionary } from '@kbn/ml-url-state'; import { LOG_RATE_ANALYSIS_TYPE, type LogRateAnalysisType, @@ -62,7 +61,6 @@ export function getDocumentCountStatsSplitLabel( export interface LogRateAnalysisContentProps { /** The data view to analyze. */ dataView: DataView; - setGlobalState?: (params: Dictionary) => void; /** Timestamp for the start of the range for initial analysis */ initialAnalysisStart?: number | WindowParameters; timeRange?: { min: Moment; max: Moment }; @@ -84,7 +82,6 @@ export interface LogRateAnalysisContentProps { export const LogRateAnalysisContent: FC = ({ dataView, - setGlobalState, initialAnalysisStart: incomingInitialAnalysisStart, timeRange, esSearchQuery = DEFAULT_SEARCH_QUERY, @@ -150,7 +147,7 @@ export const LogRateAnalysisContent: FC = ({ dataView, 'log_rate_analysis', searchQuery, - setGlobalState, + undefined, currentSelectedSignificantItem, currentSelectedGroup, undefined, diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_content_wrapper.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_content_wrapper.tsx index 62fac2312ebfd..42ae9b2cf0868 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_content_wrapper.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_content_wrapper.tsx @@ -40,8 +40,6 @@ export interface LogRateAnalysisContentWrapperProps { stickyHistogram?: boolean; /** App dependencies */ appDependencies: AiopsAppDependencies; - /** On global timefilter update */ - setGlobalState?: any; /** Timestamp for start of initial analysis */ initialAnalysisStart?: number | WindowParameters; /** Optional time range */ @@ -66,7 +64,6 @@ export interface LogRateAnalysisContentWrapperProps { export const LogRateAnalysisContentWrapper: FC = ({ dataView, appDependencies, - setGlobalState, initialAnalysisStart, timeRange, esSearchQuery, @@ -100,7 +97,6 @@ export const LogRateAnalysisContentWrapper: FC = ({ stickyHistogram }) => { embeddingOrigin={AIOPS_TELEMETRY_ID.AIOPS_DEFAULT_SOURCE} esSearchQuery={searchQuery} onWindowParametersChange={onWindowParametersHandler} - setGlobalState={setGlobalState} stickyHistogram={stickyHistogram} /> diff --git a/x-pack/plugins/aiops/public/hooks/use_data.ts b/x-pack/plugins/aiops/public/hooks/use_data.ts index 4f0e2526d7a1d..36cfdbca0f52f 100644 --- a/x-pack/plugins/aiops/public/hooks/use_data.ts +++ b/x-pack/plugins/aiops/public/hooks/use_data.ts @@ -57,7 +57,7 @@ export const useData = ( const timeRangeMemoized = useMemo( () => timefilter.getActiveBounds(), // eslint-disable-next-line react-hooks/exhaustive-deps - [JSON.stringify(timefilter.getActiveBounds())] + [lastRefresh, JSON.stringify(timefilter.getTime())] ); const fieldStatsRequest: DocumentStatsSearchStrategyParams | undefined = useMemo(() => { diff --git a/x-pack/plugins/aiops/public/hooks/use_document_count_stats.ts b/x-pack/plugins/aiops/public/hooks/use_document_count_stats.ts index c154640acd4e8..6acbb0855d253 100644 --- a/x-pack/plugins/aiops/public/hooks/use_document_count_stats.ts +++ b/x-pack/plugins/aiops/public/hooks/use_document_count_stats.ts @@ -77,13 +77,13 @@ export function useDocumentCountStats>({}); + const cacheKey = stringHash( + `${JSON.stringify(searchParams)}_${JSON.stringify(searchParamsCompare)}` + ); + const fetchDocumentCountData = useCallback(async () => { if (!searchParams) return; - const cacheKey = stringHash( - `${JSON.stringify(searchParams)}_${JSON.stringify(searchParamsCompare)}` - ); - if (documentStatsCache[cacheKey]) { setDocumentStats(documentStatsCache[cacheKey]); return; @@ -172,7 +172,9 @@ export function useDocumentCountStats { schedule: CreateBodySchema['schedule']; actions: CreateBodySchema['actions']; notify_when?: CreateBodySchema['notify_when']; - notification_delay?: CreateBodySchema['notification_delay']; + alert_delay?: CreateBodySchema['alert_delay']; } export interface CreateRuleResponse { diff --git a/x-pack/plugins/alerting/common/routes/rule/response/index.ts b/x-pack/plugins/alerting/common/routes/rule/response/index.ts index 8e405c599e483..8c784e744d473 100644 --- a/x-pack/plugins/alerting/common/routes/rule/response/index.ts +++ b/x-pack/plugins/alerting/common/routes/rule/response/index.ts @@ -37,7 +37,7 @@ export { ruleSnoozeScheduleSchema as ruleSnoozeScheduleSchemaV1, notifyWhenSchema as notifyWhenSchemaV1, scheduleIdsSchema as scheduleIdsSchemaV1, - notificationDelaySchema as notificationDelaySchemaV1, + alertDelaySchema as alertDelaySchemaV1, } from './schemas/v1'; export type { diff --git a/x-pack/plugins/alerting/common/routes/rule/response/schemas/v1.ts b/x-pack/plugins/alerting/common/routes/rule/response/schemas/v1.ts index 67f57926d5460..f485aa7374d6c 100644 --- a/x-pack/plugins/alerting/common/routes/rule/response/schemas/v1.ts +++ b/x-pack/plugins/alerting/common/routes/rule/response/schemas/v1.ts @@ -182,7 +182,7 @@ export const ruleSnoozeScheduleSchema = schema.object({ skipRecurrences: schema.maybe(schema.arrayOf(schema.string())), }); -export const notificationDelaySchema = schema.object({ +export const alertDelaySchema = schema.object({ active: schema.number(), }); @@ -218,7 +218,7 @@ export const ruleResponseSchema = schema.object({ revision: schema.number(), running: schema.maybe(schema.nullable(schema.boolean())), view_in_app_relative_url: schema.maybe(schema.nullable(schema.string())), - notification_delay: schema.maybe(notificationDelaySchema), + alert_delay: schema.maybe(alertDelaySchema), }); export const scheduleIdsSchema = schema.maybe(schema.arrayOf(schema.string())); diff --git a/x-pack/plugins/alerting/common/routes/rule/response/types/v1.ts b/x-pack/plugins/alerting/common/routes/rule/response/types/v1.ts index 453d1a96d24dc..25e08ab407ebb 100644 --- a/x-pack/plugins/alerting/common/routes/rule/response/types/v1.ts +++ b/x-pack/plugins/alerting/common/routes/rule/response/types/v1.ts @@ -53,5 +53,5 @@ export interface RuleResponse { revision: RuleResponseSchemaType['revision']; running?: RuleResponseSchemaType['running']; view_in_app_relative_url?: RuleResponseSchemaType['view_in_app_relative_url']; - notification_delay?: RuleResponseSchemaType['notification_delay']; + alert_delay?: RuleResponseSchemaType['alert_delay']; } diff --git a/x-pack/plugins/alerting/common/rule.ts b/x-pack/plugins/alerting/common/rule.ts index 590c1d4312d57..7cec5bdbdd7a6 100644 --- a/x-pack/plugins/alerting/common/rule.ts +++ b/x-pack/plugins/alerting/common/rule.ts @@ -141,7 +141,7 @@ export interface MappedParamsProperties { export type MappedParams = SavedObjectAttributes & MappedParamsProperties; -export interface NotificationDelay { +export interface AlertDelay extends SavedObjectAttributes { active: number; } @@ -178,7 +178,7 @@ export interface Rule { revision: number; running?: boolean | null; viewInAppRelativeUrl?: string; - notificationDelay?: NotificationDelay; + alertDelay?: AlertDelay; } export interface SanitizedAlertsFilter extends AlertsFilter { @@ -222,6 +222,7 @@ export type SanitizedRuleConfig = Pick< | 'muteAll' | 'revision' | 'snoozeSchedule' + | 'alertDelay' > & { producer: string; ruleTypeId: string; diff --git a/x-pack/plugins/alerting/server/alerts_client/alerts_client.test.ts b/x-pack/plugins/alerting/server/alerts_client/alerts_client.test.ts index 0ef8f7c31e019..81848c3e65686 100644 --- a/x-pack/plugins/alerting/server/alerts_client/alerts_client.test.ts +++ b/x-pack/plugins/alerting/server/alerts_client/alerts_client.test.ts @@ -308,6 +308,7 @@ describe('Alerts Client', () => { flappingSettings: DEFAULT_FLAPPING_SETTINGS, notifyOnActionGroupChange: true, maintenanceWindowIds: [], + alertDelay: 0, }; }); @@ -516,6 +517,25 @@ describe('Alerts Client', () => { }); }); + test('should not index new alerts if the activeCount is less than the rule alertDelay', async () => { + const alertsClient = new AlertsClient<{}, {}, {}, 'default', 'recovered'>({ + ...alertsClientParams, + rule: { ...alertsClientParams.rule, alertDelay: 3 }, + }); + + await alertsClient.initializeExecution(defaultExecutionOpts); + + // Report 1 new alerts + const alertExecutorService = alertsClient.factory(); + alertExecutorService.create('1').scheduleActions('default'); + + alertsClient.processAndLogAlerts(processAndLogAlertsOpts); + + await alertsClient.persistAlerts(); + + expect(clusterClient.bulk).not.toHaveBeenCalled(); + }); + test('should update ongoing alerts in existing index', async () => { clusterClient.search.mockResolvedValue({ took: 10, diff --git a/x-pack/plugins/alerting/server/alerts_client/alerts_client.ts b/x-pack/plugins/alerting/server/alerts_client/alerts_client.ts index cc443975cee46..0c1eb897a6e59 100644 --- a/x-pack/plugins/alerting/server/alerts_client/alerts_client.ts +++ b/x-pack/plugins/alerting/server/alerts_client/alerts_client.ts @@ -344,6 +344,11 @@ export class AlertsClient< }) ); } else { + // skip writing the alert document if the number of consecutive + // active alerts is less than the rule alertDelay threshold + if (activeAlerts[id].getActiveCount() < this.options.rule.alertDelay) { + continue; + } activeAlertsToIndex.push( buildNewAlert< AlertData, diff --git a/x-pack/plugins/alerting/server/alerts_client/alerts_client_fixtures.ts b/x-pack/plugins/alerting/server/alerts_client/alerts_client_fixtures.ts index 90c2f4c40b65b..0da20a5e49b70 100644 --- a/x-pack/plugins/alerting/server/alerts_client/alerts_client_fixtures.ts +++ b/x-pack/plugins/alerting/server/alerts_client/alerts_client_fixtures.ts @@ -25,6 +25,7 @@ export const alertRuleData: AlertRuleData = { revision: 0, spaceId: 'default', tags: ['rule-', '-tags'], + alertDelay: 0, }; export const mockAAD = { diff --git a/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.test.ts b/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.test.ts index 446ecfd79c8b8..d01060820ae52 100644 --- a/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.test.ts +++ b/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.test.ts @@ -245,6 +245,7 @@ describe('Legacy Alerts Client', () => { flappingSettings: DEFAULT_FLAPPING_SETTINGS, notifyOnActionGroupChange: true, maintenanceWindowIds: ['window-id1', 'window-id2'], + alertDelay: 5, }); expect(processAlerts).toHaveBeenCalledWith({ @@ -275,13 +276,15 @@ describe('Legacy Alerts Client', () => { }, true, 'default', + 5, {}, { '1': new Alert('1', testAlert1), '2': new Alert('2', testAlert2), }, {}, - {} + {}, + null ); expect(logAlerts).toHaveBeenCalledWith({ diff --git a/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.ts b/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.ts index 0de97a8a29be6..a5cfc642a19ee 100644 --- a/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.ts +++ b/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.ts @@ -140,6 +140,7 @@ export class LegacyAlertsClient< notifyOnActionGroupChange, flappingSettings, maintenanceWindowIds, + alertDelay, }: ProcessAlertsOpts) { const { newAlerts: processedAlertsNew, @@ -168,10 +169,12 @@ export class LegacyAlertsClient< flappingSettings, notifyOnActionGroupChange, this.options.ruleType.defaultActionGroupId, + alertDelay, processedAlertsNew, processedAlertsActive, trimmedAlertsRecovered, - processedAlertsRecoveredCurrent + processedAlertsRecoveredCurrent, + this.startedAtString ); alerts.currentRecoveredAlerts = merge(alerts.currentRecoveredAlerts, earlyRecoveredAlerts); @@ -203,11 +206,13 @@ export class LegacyAlertsClient< flappingSettings, notifyOnActionGroupChange, maintenanceWindowIds, + alertDelay, }: ProcessAndLogAlertsOpts) { this.processAlerts({ notifyOnActionGroupChange, flappingSettings, maintenanceWindowIds, + alertDelay, }); this.logAlerts({ diff --git a/x-pack/plugins/alerting/server/alerts_client/lib/format_rule.test.ts b/x-pack/plugins/alerting/server/alerts_client/lib/format_rule.test.ts index 9f335f8266b21..c9cc7fbefbfa4 100644 --- a/x-pack/plugins/alerting/server/alerts_client/lib/format_rule.test.ts +++ b/x-pack/plugins/alerting/server/alerts_client/lib/format_rule.test.ts @@ -61,6 +61,7 @@ describe('formatRule', () => { revision: 0, spaceId: 'default', tags: ['rule-', '-tags'], + alertDelay: 0, }, ruleType, }) diff --git a/x-pack/plugins/alerting/server/alerts_client/types.ts b/x-pack/plugins/alerting/server/alerts_client/types.ts index 44690d5881179..1de1c06f5e826 100644 --- a/x-pack/plugins/alerting/server/alerts_client/types.ts +++ b/x-pack/plugins/alerting/server/alerts_client/types.ts @@ -47,6 +47,7 @@ export interface AlertRuleData { revision: number; spaceId: string; tags: string[]; + alertDelay: number; } export interface AlertRule { @@ -111,12 +112,14 @@ export interface ProcessAndLogAlertsOpts { flappingSettings: RulesSettingsFlappingProperties; notifyOnActionGroupChange: boolean; maintenanceWindowIds: string[]; + alertDelay: number; } export interface ProcessAlertsOpts { flappingSettings: RulesSettingsFlappingProperties; notifyOnActionGroupChange: boolean; maintenanceWindowIds: string[]; + alertDelay: number; } export interface LogAlertsOpts { diff --git a/x-pack/plugins/alerting/server/alerts_service/alerts_service.test.ts b/x-pack/plugins/alerting/server/alerts_service/alerts_service.test.ts index b73f671ab0695..def19570f7e0a 100644 --- a/x-pack/plugins/alerting/server/alerts_service/alerts_service.test.ts +++ b/x-pack/plugins/alerting/server/alerts_service/alerts_service.test.ts @@ -1475,6 +1475,7 @@ describe('Alerts Service', () => { revision: 0, spaceId: 'default', tags: ['rule-', '-tags'], + alertDelay: 0, }, }); @@ -1495,6 +1496,7 @@ describe('Alerts Service', () => { revision: 0, spaceId: 'default', tags: ['rule-', '-tags'], + alertDelay: 0, }, kibanaVersion: '8.8.0', }); @@ -1528,6 +1530,7 @@ describe('Alerts Service', () => { revision: 0, spaceId: 'default', tags: ['rule-', '-tags'], + alertDelay: 0, }, }); @@ -1576,6 +1579,7 @@ describe('Alerts Service', () => { revision: 0, spaceId: 'default', tags: ['rule-', '-tags'], + alertDelay: 0, }, }); @@ -1610,6 +1614,7 @@ describe('Alerts Service', () => { revision: 0, spaceId: 'default', tags: ['rule-', '-tags'], + alertDelay: 0, }, kibanaVersion: '8.8.0', }); @@ -1674,6 +1679,7 @@ describe('Alerts Service', () => { revision: 0, spaceId: 'default', tags: ['rule-', '-tags'], + alertDelay: 0, }, }), alertsService.createAlertsClient({ @@ -1691,6 +1697,7 @@ describe('Alerts Service', () => { revision: 0, spaceId: 'default', tags: ['rule-', '-tags'], + alertDelay: 0, }, }), ]); @@ -1725,6 +1732,7 @@ describe('Alerts Service', () => { revision: 0, spaceId: 'default', tags: ['rule-', '-tags'], + alertDelay: 0, }, kibanaVersion: '8.8.0', }); @@ -1781,6 +1789,7 @@ describe('Alerts Service', () => { revision: 0, spaceId: 'default', tags: ['rule-', '-tags'], + alertDelay: 0, }, }); @@ -1801,6 +1810,7 @@ describe('Alerts Service', () => { revision: 0, spaceId: 'default', tags: ['rule-', '-tags'], + alertDelay: 0, }, kibanaVersion: '8.8.0', }); @@ -1865,6 +1875,7 @@ describe('Alerts Service', () => { revision: 0, spaceId: 'default', tags: ['rule-', '-tags'], + alertDelay: 0, }, }); }; @@ -1892,6 +1903,7 @@ describe('Alerts Service', () => { revision: 0, spaceId: 'default', tags: ['rule-', '-tags'], + alertDelay: 0, }, kibanaVersion: '8.8.0', }); @@ -1961,6 +1973,7 @@ describe('Alerts Service', () => { revision: 0, spaceId: 'default', tags: ['rule-', '-tags'], + alertDelay: 0, }, }); }; @@ -2026,6 +2039,7 @@ describe('Alerts Service', () => { revision: 0, spaceId: 'default', tags: ['rule-', '-tags'], + alertDelay: 0, }, }); @@ -2091,6 +2105,7 @@ describe('Alerts Service', () => { revision: 0, spaceId: 'default', tags: ['rule-', '-tags'], + alertDelay: 0, }, }); @@ -2154,6 +2169,7 @@ describe('Alerts Service', () => { revision: 0, spaceId: 'default', tags: ['rule-', '-tags'], + alertDelay: 0, }, }); diff --git a/x-pack/plugins/alerting/server/alerts_service/lib/set_alerts_to_untracked.ts b/x-pack/plugins/alerting/server/alerts_service/lib/set_alerts_to_untracked.ts index 4b1e67baaba91..d0e02fb8b1783 100644 --- a/x-pack/plugins/alerting/server/alerts_service/lib/set_alerts_to_untracked.ts +++ b/x-pack/plugins/alerting/server/alerts_service/lib/set_alerts_to_untracked.ts @@ -18,6 +18,7 @@ import { ALERT_STATUS_UNTRACKED, ALERT_TIME_RANGE, ALERT_UUID, + AlertStatus, } from '@kbn/rule-data-utils'; export interface SetAlertsToUntrackedOpts { @@ -39,20 +40,15 @@ interface ConsumersAndRuleTypesAggregation { }; } -export async function setAlertsToUntracked({ - logger, - esClient, - indices, +const getQuery = ({ ruleIds = [], - alertUuids = [], // OPTIONAL - If no alertUuids are passed, untrack ALL ids by default, - ensureAuthorized, + alertUuids = [], + alertStatus, }: { - logger: Logger; - esClient: ElasticsearchClient; -} & SetAlertsToUntrackedOpts): Promise { - if (isEmpty(ruleIds) && isEmpty(alertUuids)) - throw new Error('Must provide either ruleIds or alertUuids'); - + ruleIds?: string[]; + alertUuids?: string[]; + alertStatus: AlertStatus; +}) => { const shouldMatchRules: Array<{ term: Record }> = ruleIds.map( (ruleId) => ({ term: { @@ -71,12 +67,12 @@ export async function setAlertsToUntracked({ const statusTerms: Array<{ term: Record }> = [ { term: { - [ALERT_STATUS]: { value: ALERT_STATUS_ACTIVE }, + [ALERT_STATUS]: { value: alertStatus }, }, }, ]; - const must = [ + return [ ...statusTerms, { bool: { @@ -90,6 +86,21 @@ export async function setAlertsToUntracked({ }, }, ]; +}; + +export async function setAlertsToUntracked({ + logger, + esClient, + indices, + ruleIds = [], + alertUuids = [], // OPTIONAL - If no alertUuids are passed, untrack ALL ids by default, + ensureAuthorized, +}: { + logger: Logger; + esClient: ElasticsearchClient; +} & SetAlertsToUntrackedOpts): Promise { + if (isEmpty(ruleIds) && isEmpty(alertUuids)) + throw new Error('Must provide either ruleIds or alertUuids'); if (ensureAuthorized) { // Fetch all rule type IDs and rule consumers, then run the provided ensureAuthorized check for each of them @@ -100,7 +111,11 @@ export async function setAlertsToUntracked({ size: 0, query: { bool: { - must, + must: getQuery({ + ruleIds, + alertUuids, + alertStatus: ALERT_STATUS_ACTIVE, + }), }, }, aggs: { @@ -141,7 +156,11 @@ export async function setAlertsToUntracked({ }, query: { bool: { - must, + must: getQuery({ + ruleIds, + alertUuids, + alertStatus: ALERT_STATUS_ACTIVE, + }), }, }, }, @@ -169,7 +188,11 @@ export async function setAlertsToUntracked({ size: total, query: { bool: { - must, + must: getQuery({ + ruleIds, + alertUuids, + alertStatus: ALERT_STATUS_UNTRACKED, + }), }, }, }, diff --git a/x-pack/plugins/alerting/server/application/rule/methods/create/schemas/create_rule_data_schema.ts b/x-pack/plugins/alerting/server/application/rule/methods/create/schemas/create_rule_data_schema.ts index 44a89e05992fe..9d1823381d17d 100644 --- a/x-pack/plugins/alerting/server/application/rule/methods/create/schemas/create_rule_data_schema.ts +++ b/x-pack/plugins/alerting/server/application/rule/methods/create/schemas/create_rule_data_schema.ts @@ -7,11 +7,7 @@ import { schema } from '@kbn/config-schema'; import { validateDuration } from '../../../validation'; -import { - notifyWhenSchema, - actionAlertsFilterSchema, - notificationDelaySchema, -} from '../../../schemas'; +import { notifyWhenSchema, actionAlertsFilterSchema, alertDelaySchema } from '../../../schemas'; export const createRuleDataSchema = schema.object({ name: schema.string(), @@ -44,5 +40,5 @@ export const createRuleDataSchema = schema.object({ { defaultValue: [] } ), notifyWhen: schema.maybe(schema.nullable(notifyWhenSchema)), - notificationDelay: schema.maybe(notificationDelaySchema), + alertDelay: schema.maybe(alertDelaySchema), }); diff --git a/x-pack/plugins/alerting/server/application/rule/methods/create/types/create_rule_data.ts b/x-pack/plugins/alerting/server/application/rule/methods/create/types/create_rule_data.ts index f99beda90e80a..abee30ec9a524 100644 --- a/x-pack/plugins/alerting/server/application/rule/methods/create/types/create_rule_data.ts +++ b/x-pack/plugins/alerting/server/application/rule/methods/create/types/create_rule_data.ts @@ -22,5 +22,5 @@ export interface CreateRuleData { schedule: CreateRuleDataType['schedule']; actions: CreateRuleDataType['actions']; notifyWhen?: CreateRuleDataType['notifyWhen']; - notificationDelay?: CreateRuleDataType['notificationDelay']; + alertDelay?: CreateRuleDataType['alertDelay']; } diff --git a/x-pack/plugins/alerting/server/application/rule/schemas/index.ts b/x-pack/plugins/alerting/server/application/rule/schemas/index.ts index d039d190f1e96..06645e90d7baf 100644 --- a/x-pack/plugins/alerting/server/application/rule/schemas/index.ts +++ b/x-pack/plugins/alerting/server/application/rule/schemas/index.ts @@ -13,7 +13,7 @@ export { monitoringSchema, ruleSchema, ruleDomainSchema, - notificationDelaySchema, + alertDelaySchema, } from './rule_schemas'; export { diff --git a/x-pack/plugins/alerting/server/application/rule/schemas/rule_schemas.ts b/x-pack/plugins/alerting/server/application/rule/schemas/rule_schemas.ts index b75a6e4f76aad..6041e475daf52 100644 --- a/x-pack/plugins/alerting/server/application/rule/schemas/rule_schemas.ts +++ b/x-pack/plugins/alerting/server/application/rule/schemas/rule_schemas.ts @@ -132,7 +132,7 @@ export const snoozeScheduleSchema = schema.object({ skipRecurrences: schema.maybe(schema.arrayOf(schema.string())), }); -export const notificationDelaySchema = schema.object({ +export const alertDelaySchema = schema.object({ active: schema.number(), }); @@ -172,7 +172,7 @@ export const ruleDomainSchema = schema.object({ revision: schema.number(), running: schema.maybe(schema.nullable(schema.boolean())), viewInAppRelativeUrl: schema.maybe(schema.nullable(schema.string())), - notificationDelay: schema.maybe(notificationDelaySchema), + alertDelay: schema.maybe(alertDelaySchema), }); /** @@ -210,5 +210,5 @@ export const ruleSchema = schema.object({ revision: schema.number(), running: schema.maybe(schema.nullable(schema.boolean())), viewInAppRelativeUrl: schema.maybe(schema.nullable(schema.string())), - notificationDelay: schema.maybe(notificationDelaySchema), + alertDelay: schema.maybe(alertDelaySchema), }); diff --git a/x-pack/plugins/alerting/server/application/rule/transforms/transform_rule_attributes_to_rule_domain.ts b/x-pack/plugins/alerting/server/application/rule/transforms/transform_rule_attributes_to_rule_domain.ts index ea331cec4fcc3..9cc8131b1fa2d 100644 --- a/x-pack/plugins/alerting/server/application/rule/transforms/transform_rule_attributes_to_rule_domain.ts +++ b/x-pack/plugins/alerting/server/application/rule/transforms/transform_rule_attributes_to_rule_domain.ts @@ -216,7 +216,7 @@ export const transformRuleAttributesToRuleDomain = ( revision: ruleDomain.revision, running: ruleDomain.running, viewInAppRelativeUrl: ruleDomain.viewInAppRelativeUrl, - notificationDelay: ruleDomain.notificationDelay, + alertDelay: ruleDomain.alertDelay, }; if (isPublic) { diff --git a/x-pack/plugins/alerting/server/application/rule/transforms/transform_rule_domain_to_rule_attributes.ts b/x-pack/plugins/alerting/server/application/rule/transforms/transform_rule_domain_to_rule_attributes.ts index 2eb21c42587d3..e527bd6f31df0 100644 --- a/x-pack/plugins/alerting/server/application/rule/transforms/transform_rule_domain_to_rule_attributes.ts +++ b/x-pack/plugins/alerting/server/application/rule/transforms/transform_rule_domain_to_rule_attributes.ts @@ -68,6 +68,6 @@ export const transformRuleDomainToRuleAttributes = ( ...(rule.nextRun !== undefined ? { nextRun: rule.nextRun?.toISOString() || null } : {}), revision: rule.revision, ...(rule.running !== undefined ? { running: rule.running } : {}), - ...(rule.notificationDelay !== undefined ? { notificationDelay: rule.notificationDelay } : {}), + ...(rule.alertDelay !== undefined ? { alertDelay: rule.alertDelay } : {}), }; }; diff --git a/x-pack/plugins/alerting/server/application/rule/types/rule.ts b/x-pack/plugins/alerting/server/application/rule/types/rule.ts index 1a7e7e1d37118..f59056b382440 100644 --- a/x-pack/plugins/alerting/server/application/rule/types/rule.ts +++ b/x-pack/plugins/alerting/server/application/rule/types/rule.ts @@ -85,7 +85,7 @@ export interface Rule { revision: RuleSchemaType['revision']; running?: RuleSchemaType['running']; viewInAppRelativeUrl?: RuleSchemaType['viewInAppRelativeUrl']; - notificationDelay?: RuleSchemaType['notificationDelay']; + alertDelay?: RuleSchemaType['alertDelay']; } export interface RuleDomain { @@ -121,5 +121,5 @@ export interface RuleDomain { revision: RuleDomainSchemaType['revision']; running?: RuleDomainSchemaType['running']; viewInAppRelativeUrl?: RuleDomainSchemaType['viewInAppRelativeUrl']; - notificationDelay?: RuleSchemaType['notificationDelay']; + alertDelay?: RuleSchemaType['alertDelay']; } diff --git a/x-pack/plugins/alerting/server/data/rule/types/rule_attributes.ts b/x-pack/plugins/alerting/server/data/rule/types/rule_attributes.ts index 316578149c5ca..e047be1b9cddf 100644 --- a/x-pack/plugins/alerting/server/data/rule/types/rule_attributes.ts +++ b/x-pack/plugins/alerting/server/data/rule/types/rule_attributes.ts @@ -142,7 +142,7 @@ interface RuleMetaAttributes { versionApiKeyLastmodified?: string; } -interface NotificationDelayAttributes { +interface AlertDelayAttributes { active: number; } @@ -178,5 +178,5 @@ export interface RuleAttributes { nextRun?: string | null; revision: number; running?: boolean | null; - notificationDelay?: NotificationDelayAttributes; + alertDelay?: AlertDelayAttributes; } diff --git a/x-pack/plugins/alerting/server/integration_tests/__snapshots__/serverless_upgrade_and_rollback_checks.test.ts.snap b/x-pack/plugins/alerting/server/integration_tests/__snapshots__/serverless_upgrade_and_rollback_checks.test.ts.snap new file mode 100644 index 0000000000000..70ffc475d01d6 --- /dev/null +++ b/x-pack/plugins/alerting/server/integration_tests/__snapshots__/serverless_upgrade_and_rollback_checks.test.ts.snap @@ -0,0 +1,9829 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Serverless upgrade and rollback checks detect param changes to review for: .es-query 1`] = ` +Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "aggField": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "aggType": Object { + "flags": Object { + "default": "count", + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "esQuery": Object { + "flags": Object { + "error": [Function], + }, + "type": "any", + "whens": Array [ + Object { + "is": Object { + "allow": Array [ + "esQuery", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + "otherwise": Object { + "flags": Object { + "error": [Function], + "presence": "forbidden", + }, + "type": "any", + }, + "ref": Object { + "path": Array [ + "searchType", + ], + }, + "then": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + }, + ], + }, + "esqlQuery": Object { + "flags": Object { + "error": [Function], + }, + "type": "any", + "whens": Array [ + Object { + "is": Object { + "allow": Array [ + "esqlQuery", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + "otherwise": Object { + "flags": Object { + "error": [Function], + "presence": "forbidden", + }, + "type": "any", + }, + "ref": Object { + "path": Array [ + "searchType", + ], + }, + "then": Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "esql": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", + }, + }, + ], + }, + "excludeHitsFromPreviousRun": Object { + "flags": Object { + "default": true, + "error": [Function], + "presence": "optional", + }, + "type": "boolean", + }, + "groupBy": Object { + "flags": Object { + "default": "all", + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "index": Object { + "flags": Object { + "error": [Function], + }, + "type": "any", + "whens": Array [ + Object { + "is": Object { + "allow": Array [ + "esQuery", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + "otherwise": Object { + "flags": Object { + "error": [Function], + "presence": "forbidden", + }, + "type": "any", + }, + "ref": Object { + "path": Array [ + "searchType", + ], + }, + "then": Object { + "flags": Object { + "error": [Function], + }, + "items": Array [ + Object { + "flags": Object { + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + ], + "rules": Array [ + Object { + "args": Object { + "limit": 1, + }, + "name": "min", + }, + ], + "type": "array", + }, + }, + ], + }, + "searchConfiguration": Object { + "flags": Object { + "error": [Function], + }, + "type": "any", + "whens": Array [ + Object { + "is": Object { + "allow": Array [ + "searchSource", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + "otherwise": Object { + "flags": Object { + "error": [Function], + "presence": "forbidden", + }, + "type": "any", + }, + "ref": Object { + "path": Array [ + "searchType", + ], + }, + "then": Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + "unknown": true, + }, + "keys": Object {}, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", + }, + }, + ], + }, + "searchType": Object { + "flags": Object { + "default": "esQuery", + "error": [Function], + "presence": "optional", + }, + "matches": Array [ + Object { + "schema": Object { + "allow": Array [ + "searchSource", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + Object { + "schema": Object { + "allow": Array [ + "esQuery", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + Object { + "schema": Object { + "allow": Array [ + "esqlQuery", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + ], + "type": "alternatives", + }, + "size": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "limit": 0, + }, + "name": "min", + }, + Object { + "args": Object { + "limit": 10000, + }, + "name": "max", + }, + ], + "type": "number", + }, + "sourceFields": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "items": Array [ + Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "label": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "searchPath": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", + }, + ], + "rules": Array [ + Object { + "args": Object { + "limit": 5, + }, + "name": "max", + }, + ], + "type": "array", + }, + "termField": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "matches": Array [ + Object { + "schema": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + }, + Object { + "schema": Object { + "flags": Object { + "error": [Function], + }, + "items": Array [ + Object { + "flags": Object { + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + ], + "rules": Array [ + Object { + "args": Object { + "limit": 2, + }, + "name": "min", + }, + Object { + "args": Object { + "limit": 4, + }, + "name": "max", + }, + ], + "type": "array", + }, + }, + ], + "type": "alternatives", + }, + "termSize": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "limit": 1, + }, + "name": "min", + }, + ], + "type": "number", + }, + "threshold": Object { + "flags": Object { + "error": [Function], + }, + "items": Array [ + Object { + "flags": Object { + "error": [Function], + "presence": "optional", + }, + "type": "number", + }, + ], + "rules": Array [ + Object { + "args": Object { + "limit": 1, + }, + "name": "min", + }, + Object { + "args": Object { + "limit": 2, + }, + "name": "max", + }, + ], + "type": "array", + }, + "thresholdComparator": Object { + "flags": Object { + "error": [Function], + }, + "matches": Array [ + Object { + "schema": Object { + "allow": Array [ + ">", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + Object { + "schema": Object { + "allow": Array [ + "<", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + Object { + "schema": Object { + "allow": Array [ + ">=", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + Object { + "schema": Object { + "allow": Array [ + "<=", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + Object { + "schema": Object { + "allow": Array [ + "between", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + Object { + "schema": Object { + "allow": Array [ + "notBetween", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + ], + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "alternatives", + }, + "timeField": Object { + "flags": Object { + "error": [Function], + }, + "type": "any", + "whens": Array [ + Object { + "is": Object { + "allow": Array [ + "esQuery", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + "otherwise": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "ref": Object { + "path": Array [ + "searchType", + ], + }, + "then": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + }, + ], + }, + "timeWindowSize": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "limit": 1, + }, + "name": "min", + }, + ], + "type": "number", + }, + "timeWindowUnit": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "object", +} +`; + +exports[`Serverless upgrade and rollback checks detect param changes to review for: .geo-containment 1`] = ` +Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "boundaryGeoField": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "boundaryIndexId": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "boundaryIndexQuery": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "type": "any", + }, + "boundaryIndexTitle": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "boundaryNameField": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "boundaryType": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "dateField": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "entity": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "geoField": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "index": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "indexId": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "indexQuery": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "type": "any", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", +} +`; + +exports[`Serverless upgrade and rollback checks detect param changes to review for: .index-threshold 1`] = ` +Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "aggField": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "aggType": Object { + "flags": Object { + "default": "count", + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "filterKuery": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "groupBy": Object { + "flags": Object { + "default": "all", + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "index": Object { + "flags": Object { + "error": [Function], + }, + "matches": Array [ + Object { + "schema": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + }, + Object { + "schema": Object { + "flags": Object { + "error": [Function], + }, + "items": Array [ + Object { + "flags": Object { + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + ], + "rules": Array [ + Object { + "args": Object { + "limit": 1, + }, + "name": "min", + }, + ], + "type": "array", + }, + }, + ], + "type": "alternatives", + }, + "termField": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "termSize": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "limit": 1, + }, + "name": "min", + }, + ], + "type": "number", + }, + "threshold": Object { + "flags": Object { + "error": [Function], + }, + "items": Array [ + Object { + "flags": Object { + "error": [Function], + "presence": "optional", + }, + "type": "number", + }, + ], + "rules": Array [ + Object { + "args": Object { + "limit": 1, + }, + "name": "min", + }, + Object { + "args": Object { + "limit": 2, + }, + "name": "max", + }, + ], + "type": "array", + }, + "thresholdComparator": Object { + "flags": Object { + "error": [Function], + }, + "matches": Array [ + Object { + "schema": Object { + "allow": Array [ + ">", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + Object { + "schema": Object { + "allow": Array [ + "<", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + Object { + "schema": Object { + "allow": Array [ + ">=", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + Object { + "schema": Object { + "allow": Array [ + "<=", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + Object { + "schema": Object { + "allow": Array [ + "between", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + Object { + "schema": Object { + "allow": Array [ + "notBetween", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + ], + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "alternatives", + }, + "timeField": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "timeWindowSize": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "limit": 1, + }, + "name": "min", + }, + ], + "type": "number", + }, + "timeWindowUnit": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "object", +} +`; + +exports[`Serverless upgrade and rollback checks detect param changes to review for: apm.anomaly 1`] = ` +Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "anomalyDetectorTypes": Object { + "flags": Object { + "error": [Function], + }, + "items": Array [ + Object { + "flags": Object { + "error": [Function], + "presence": "optional", + }, + "matches": Array [ + Object { + "schema": Object { + "allow": Array [ + "txLatency", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + Object { + "schema": Object { + "allow": Array [ + "txThroughput", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + Object { + "schema": Object { + "allow": Array [ + "txFailureRate", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + ], + "type": "alternatives", + }, + ], + "rules": Array [ + Object { + "args": Object { + "limit": 1, + }, + "name": "min", + }, + ], + "type": "array", + }, + "anomalySeverityType": Object { + "flags": Object { + "error": [Function], + }, + "matches": Array [ + Object { + "schema": Object { + "allow": Array [ + "critical", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + Object { + "schema": Object { + "allow": Array [ + "major", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + Object { + "schema": Object { + "allow": Array [ + "minor", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + Object { + "schema": Object { + "allow": Array [ + "warning", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + ], + "type": "alternatives", + }, + "environment": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "serviceName": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "transactionType": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "windowSize": Object { + "flags": Object { + "error": [Function], + }, + "type": "number", + }, + "windowUnit": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", +} +`; + +exports[`Serverless upgrade and rollback checks detect param changes to review for: apm.error_rate 1`] = ` +Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "environment": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "errorGroupingKey": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "groupBy": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "items": Array [ + Object { + "flags": Object { + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + ], + "type": "array", + }, + "searchConfiguration": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "query": Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "language": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "query": Object { + "flags": Object { + "error": [Function], + }, + "matches": Array [ + Object { + "schema": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + }, + Object { + "schema": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "key": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "value": Object { + "flags": Object { + "error": [Function], + }, + "type": "any", + }, + }, + "name": "entries", + }, + ], + "type": "record", + }, + }, + ], + "type": "alternatives", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", + }, + "serviceName": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "threshold": Object { + "flags": Object { + "error": [Function], + }, + "type": "number", + }, + "useKqlFilter": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "type": "boolean", + }, + "windowSize": Object { + "flags": Object { + "error": [Function], + }, + "type": "number", + }, + "windowUnit": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", +} +`; + +exports[`Serverless upgrade and rollback checks detect param changes to review for: apm.transaction_duration 1`] = ` +Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "aggregationType": Object { + "flags": Object { + "error": [Function], + }, + "matches": Array [ + Object { + "schema": Object { + "allow": Array [ + "avg", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + Object { + "schema": Object { + "allow": Array [ + "95th", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + Object { + "schema": Object { + "allow": Array [ + "99th", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + ], + "type": "alternatives", + }, + "environment": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "groupBy": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "items": Array [ + Object { + "flags": Object { + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + ], + "type": "array", + }, + "searchConfiguration": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "query": Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "language": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "query": Object { + "flags": Object { + "error": [Function], + }, + "matches": Array [ + Object { + "schema": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + }, + Object { + "schema": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "key": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "value": Object { + "flags": Object { + "error": [Function], + }, + "type": "any", + }, + }, + "name": "entries", + }, + ], + "type": "record", + }, + }, + ], + "type": "alternatives", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", + }, + "serviceName": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "threshold": Object { + "flags": Object { + "error": [Function], + }, + "type": "number", + }, + "transactionName": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "transactionType": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "useKqlFilter": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "type": "boolean", + }, + "windowSize": Object { + "flags": Object { + "error": [Function], + }, + "type": "number", + }, + "windowUnit": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", +} +`; + +exports[`Serverless upgrade and rollback checks detect param changes to review for: apm.transaction_error_rate 1`] = ` +Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "environment": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "groupBy": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "items": Array [ + Object { + "flags": Object { + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + ], + "type": "array", + }, + "searchConfiguration": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "query": Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "language": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "query": Object { + "flags": Object { + "error": [Function], + }, + "matches": Array [ + Object { + "schema": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + }, + Object { + "schema": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "key": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "value": Object { + "flags": Object { + "error": [Function], + }, + "type": "any", + }, + }, + "name": "entries", + }, + ], + "type": "record", + }, + }, + ], + "type": "alternatives", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", + }, + "serviceName": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "threshold": Object { + "flags": Object { + "error": [Function], + }, + "type": "number", + }, + "transactionName": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "transactionType": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "useKqlFilter": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "type": "boolean", + }, + "windowSize": Object { + "flags": Object { + "error": [Function], + }, + "type": "number", + }, + "windowUnit": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", +} +`; + +exports[`Serverless upgrade and rollback checks detect param changes to review for: metrics.alert.inventory.threshold 1`] = ` +Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + "unknown": true, + }, + "keys": Object { + "alertOnNoData": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "type": "boolean", + }, + "criteria": Object { + "flags": Object { + "error": [Function], + }, + "items": Array [ + Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "comparator": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "customMetric": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "aggregation": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "field": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "id": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "label": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "type": Object { + "allow": Array [ + "custom", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", + }, + "metric": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "threshold": Object { + "flags": Object { + "error": [Function], + }, + "items": Array [ + Object { + "flags": Object { + "error": [Function], + "presence": "optional", + }, + "type": "number", + }, + ], + "type": "array", + }, + "timeSize": Object { + "flags": Object { + "error": [Function], + }, + "type": "number", + }, + "timeUnit": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "warningComparator": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "warningThreshold": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "items": Array [ + Object { + "flags": Object { + "error": [Function], + "presence": "optional", + }, + "type": "number", + }, + ], + "type": "array", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", + }, + ], + "type": "array", + }, + "filterQuery": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "nodeType": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "sourceId": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", +} +`; + +exports[`Serverless upgrade and rollback checks detect param changes to review for: observability.rules.custom_threshold 1`] = ` +Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + "unknown": true, + }, + "keys": Object { + "alertOnGroupDisappear": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "type": "boolean", + }, + "alertOnNoData": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "type": "boolean", + }, + "criteria": Object { + "flags": Object { + "error": [Function], + }, + "items": Array [ + Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "aggType": Object { + "allow": Array [ + "custom", + ], + "flags": Object { + "default": [Function], + "error": [Function], + "only": true, + "presence": "optional", + }, + "type": "any", + }, + "comparator": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "equation": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "label": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "metric": Object { + "flags": Object { + "error": [Function], + "presence": "forbidden", + }, + "type": "any", + }, + "metrics": Object { + "flags": Object { + "error": [Function], + }, + "items": Array [ + Object { + "flags": Object { + "error": [Function], + "presence": "optional", + }, + "matches": Array [ + Object { + "schema": Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "aggType": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "field": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "filter": Object { + "flags": Object { + "error": [Function], + "presence": "forbidden", + }, + "type": "any", + }, + "name": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", + }, + }, + Object { + "schema": Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "aggType": Object { + "allow": Array [ + "count", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + "field": Object { + "flags": Object { + "error": [Function], + "presence": "forbidden", + }, + "type": "any", + }, + "filter": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "name": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", + }, + }, + ], + "type": "alternatives", + }, + ], + "type": "array", + }, + "threshold": Object { + "flags": Object { + "error": [Function], + }, + "items": Array [ + Object { + "flags": Object { + "error": [Function], + "presence": "optional", + }, + "type": "number", + }, + ], + "type": "array", + }, + "timeSize": Object { + "flags": Object { + "error": [Function], + }, + "type": "number", + }, + "timeUnit": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", + }, + ], + "type": "array", + }, + "groupBy": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "matches": Array [ + Object { + "schema": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + }, + Object { + "schema": Object { + "flags": Object { + "error": [Function], + }, + "items": Array [ + Object { + "flags": Object { + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + ], + "type": "array", + }, + }, + ], + "type": "alternatives", + }, + "searchConfiguration": Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "index": Object { + "flags": Object { + "error": [Function], + }, + "matches": Array [ + Object { + "schema": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + }, + Object { + "schema": Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "allowHidden": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "type": "boolean", + }, + "allowNoIndex": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "type": "boolean", + }, + "fieldAttrs": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "key": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "value": Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "count": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "type": "number", + }, + "customLabel": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", + }, + }, + "name": "entries", + }, + ], + "type": "record", + }, + "fieldFormats": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "key": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "value": Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "id": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "params": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "type": "any", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", + }, + }, + "name": "entries", + }, + ], + "type": "record", + }, + "fields": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "key": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "value": Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "aggregatable": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "type": "boolean", + }, + "count": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "limit": 0, + }, + "name": "min", + }, + ], + "type": "number", + }, + "customLabel": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "esTypes": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "items": Array [ + Object { + "flags": Object { + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + ], + "type": "array", + }, + "format": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "id": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "params": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "type": "any", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", + }, + "name": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "readFromDocValues": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "type": "boolean", + }, + "runtimeField": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "matches": Array [ + Object { + "schema": Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "customLabel": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "format": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "id": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "params": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "type": "any", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", + }, + "popularity": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "limit": 0, + }, + "name": "min", + }, + ], + "type": "number", + }, + "script": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "source": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", + }, + "type": Object { + "flags": Object { + "error": [Function], + }, + "matches": Array [ + Object { + "schema": Object { + "allow": Array [ + "keyword", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + Object { + "schema": Object { + "allow": Array [ + "long", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + Object { + "schema": Object { + "allow": Array [ + "double", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + Object { + "schema": Object { + "allow": Array [ + "date", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + Object { + "schema": Object { + "allow": Array [ + "ip", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + Object { + "schema": Object { + "allow": Array [ + "boolean", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + Object { + "schema": Object { + "allow": Array [ + "geo_point", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + ], + "type": "alternatives", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", + }, + }, + Object { + "schema": Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "fields": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "key": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "value": Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "customLabel": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "format": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "id": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "params": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "type": "any", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", + }, + "popularity": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "limit": 0, + }, + "name": "min", + }, + ], + "type": "number", + }, + "type": Object { + "flags": Object { + "error": [Function], + }, + "matches": Array [ + Object { + "schema": Object { + "allow": Array [ + "keyword", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + Object { + "schema": Object { + "allow": Array [ + "long", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + Object { + "schema": Object { + "allow": Array [ + "double", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + Object { + "schema": Object { + "allow": Array [ + "date", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + Object { + "schema": Object { + "allow": Array [ + "ip", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + Object { + "schema": Object { + "allow": Array [ + "boolean", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + Object { + "schema": Object { + "allow": Array [ + "geo_point", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + ], + "type": "alternatives", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", + }, + }, + "name": "entries", + }, + ], + "type": "record", + }, + "script": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "source": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", + }, + "type": Object { + "allow": Array [ + "composite", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", + }, + }, + ], + "type": "alternatives", + }, + "script": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "scripted": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "type": "boolean", + }, + "searchable": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "type": "boolean", + }, + "shortDotsEnable": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "type": "boolean", + }, + "subType": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "multi": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "parent": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", + }, + "nested": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "path": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", + }, + "type": Object { + "flags": Object { + "default": "string", + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": true, + }, + }, + "type": "object", + }, + }, + "name": "entries", + }, + ], + "type": "record", + }, + "id": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "name": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "namespaces": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "items": Array [ + Object { + "flags": Object { + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + ], + "type": "array", + }, + "runtimeFieldMap": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "key": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "value": Object { + "flags": Object { + "error": [Function], + }, + "matches": Array [ + Object { + "schema": Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "customLabel": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "format": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "id": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "params": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "type": "any", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", + }, + "popularity": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "limit": 0, + }, + "name": "min", + }, + ], + "type": "number", + }, + "script": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "source": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", + }, + "type": Object { + "flags": Object { + "error": [Function], + }, + "matches": Array [ + Object { + "schema": Object { + "allow": Array [ + "keyword", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + Object { + "schema": Object { + "allow": Array [ + "long", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + Object { + "schema": Object { + "allow": Array [ + "double", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + Object { + "schema": Object { + "allow": Array [ + "date", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + Object { + "schema": Object { + "allow": Array [ + "ip", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + Object { + "schema": Object { + "allow": Array [ + "boolean", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + Object { + "schema": Object { + "allow": Array [ + "geo_point", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + ], + "type": "alternatives", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", + }, + }, + Object { + "schema": Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "fields": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "key": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "value": Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "customLabel": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "format": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "id": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "params": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "type": "any", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", + }, + "popularity": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "limit": 0, + }, + "name": "min", + }, + ], + "type": "number", + }, + "type": Object { + "flags": Object { + "error": [Function], + }, + "matches": Array [ + Object { + "schema": Object { + "allow": Array [ + "keyword", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + Object { + "schema": Object { + "allow": Array [ + "long", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + Object { + "schema": Object { + "allow": Array [ + "double", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + Object { + "schema": Object { + "allow": Array [ + "date", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + Object { + "schema": Object { + "allow": Array [ + "ip", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + Object { + "schema": Object { + "allow": Array [ + "boolean", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + Object { + "schema": Object { + "allow": Array [ + "geo_point", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + ], + "type": "alternatives", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", + }, + }, + "name": "entries", + }, + ], + "type": "record", + }, + "script": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "source": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", + }, + "type": Object { + "allow": Array [ + "composite", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", + }, + }, + ], + "type": "alternatives", + }, + }, + "name": "entries", + }, + ], + "type": "record", + }, + "sourceFilters": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "items": Array [ + Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "clientId": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "matches": Array [ + Object { + "schema": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + }, + Object { + "schema": Object { + "flags": Object { + "error": [Function], + }, + "type": "number", + }, + }, + ], + "type": "alternatives", + }, + "value": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", + }, + ], + "type": "array", + }, + "timeFieldName": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "title": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "type": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "typeMeta": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + "unknown": true, + }, + "keys": Object {}, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", + }, + "version": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", + }, + }, + ], + "type": "alternatives", + }, + "query": Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "language": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "query": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", +} +`; + +exports[`Serverless upgrade and rollback checks detect param changes to review for: siem.eqlRule 1`] = ` +Object { + "$schema": "http://json-schema.org/draft-07/schema#", + "allOf": Array [ + Object { + "properties": Object { + "author": Object { + "items": Object { + "type": "string", + }, + "type": "array", + }, + "buildingBlockType": Object { + "type": "string", + }, + "description": Object { + "minLength": 1, + "type": "string", + }, + "exceptionsList": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "id": Object { + "$ref": "#/allOf/0/properties/investigationFields/anyOf/0/properties/field_names/items", + }, + "list_id": Object { + "$ref": "#/allOf/0/properties/investigationFields/anyOf/0/properties/field_names/items", + }, + "namespace_type": Object { + "enum": Array [ + "agnostic", + "single", + ], + "type": "string", + }, + "type": Object { + "enum": Array [ + "detection", + "rule_default", + "endpoint", + "endpoint_trusted_apps", + "endpoint_events", + "endpoint_host_isolation_exceptions", + "endpoint_blocklists", + ], + "type": "string", + }, + }, + "required": Array [ + "id", + "list_id", + "type", + "namespace_type", + ], + "type": "object", + }, + "type": "array", + }, + "falsePositives": Object { + "items": Object { + "type": "string", + }, + "type": "array", + }, + "from": Object { + "type": "string", + }, + "immutable": Object { + "type": "boolean", + }, + "investigationFields": Object { + "anyOf": Array [ + Object { + "additionalProperties": false, + "properties": Object { + "field_names": Object { + "items": Object { + "minLength": 1, + "pattern": "^(?! *$).+$", + "type": "string", + }, + "minItems": 1, + "type": "array", + }, + }, + "required": Array [ + "field_names", + ], + "type": "object", + }, + Object { + "items": Object { + "type": "string", + }, + "type": "array", + }, + ], + }, + "license": Object { + "type": "string", + }, + "maxSignals": Object { + "minimum": 1, + "type": "integer", + }, + "meta": Object { + "additionalProperties": Object {}, + "properties": Object {}, + "type": "object", + }, + "namespace": Object { + "type": "string", + }, + "note": Object { + "type": "string", + }, + "outputIndex": Object { + "type": "string", + }, + "references": Object { + "items": Object { + "type": "string", + }, + "type": "array", + }, + "relatedIntegrations": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "integration": Object { + "$ref": "#/allOf/0/properties/investigationFields/anyOf/0/properties/field_names/items", + }, + "package": Object { + "$ref": "#/allOf/0/properties/investigationFields/anyOf/0/properties/field_names/items", + }, + "version": Object { + "$ref": "#/allOf/0/properties/investigationFields/anyOf/0/properties/field_names/items", + }, + }, + "required": Array [ + "package", + "version", + ], + "type": "object", + }, + "type": "array", + }, + "requiredFields": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "ecs": Object { + "type": "boolean", + }, + "name": Object { + "$ref": "#/allOf/0/properties/investigationFields/anyOf/0/properties/field_names/items", + }, + "type": Object { + "$ref": "#/allOf/0/properties/investigationFields/anyOf/0/properties/field_names/items", + }, + }, + "required": Array [ + "name", + "type", + "ecs", + ], + "type": "object", + }, + "type": "array", + }, + "riskScore": Object { + "maximum": 100, + "minimum": 0, + "type": "integer", + }, + "riskScoreMapping": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "field": Object { + "type": "string", + }, + "operator": Object { + "const": "equals", + "type": "string", + }, + "risk_score": Object { + "$ref": "#/allOf/0/properties/riskScore", + }, + "value": Object { + "type": "string", + }, + }, + "required": Array [ + "field", + "operator", + "value", + ], + "type": "object", + }, + "type": "array", + }, + "ruleId": Object { + "type": "string", + }, + "ruleNameOverride": Object { + "type": "string", + }, + "setup": Object { + "type": "string", + }, + "severity": Object { + "enum": Array [ + "low", + "medium", + "high", + "critical", + ], + "type": "string", + }, + "severityMapping": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "field": Object { + "type": "string", + }, + "operator": Object { + "const": "equals", + "type": "string", + }, + "severity": Object { + "$ref": "#/allOf/0/properties/severity", + }, + "value": Object { + "type": "string", + }, + }, + "required": Array [ + "field", + "operator", + "severity", + "value", + ], + "type": "object", + }, + "type": "array", + }, + "threat": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "framework": Object { + "type": "string", + }, + "tactic": Object { + "additionalProperties": false, + "properties": Object { + "id": Object { + "type": "string", + }, + "name": Object { + "type": "string", + }, + "reference": Object { + "type": "string", + }, + }, + "required": Array [ + "id", + "name", + "reference", + ], + "type": "object", + }, + "technique": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "id": Object { + "type": "string", + }, + "name": Object { + "type": "string", + }, + "reference": Object { + "type": "string", + }, + "subtechnique": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "id": Object { + "type": "string", + }, + "name": Object { + "type": "string", + }, + "reference": Object { + "type": "string", + }, + }, + "required": Array [ + "id", + "name", + "reference", + ], + "type": "object", + }, + "type": "array", + }, + }, + "required": Array [ + "id", + "name", + "reference", + ], + "type": "object", + }, + "type": "array", + }, + }, + "required": Array [ + "framework", + "tactic", + ], + "type": "object", + }, + "type": "array", + }, + "timelineId": Object { + "type": "string", + }, + "timelineTitle": Object { + "type": "string", + }, + "timestampOverride": Object { + "type": "string", + }, + "timestampOverrideFallbackDisabled": Object { + "type": "boolean", + }, + "to": Object { + "type": "string", + }, + "version": Object { + "minimum": 1, + "type": "integer", + }, + }, + "required": Array [ + "author", + "description", + "falsePositives", + "from", + "ruleId", + "immutable", + "outputIndex", + "maxSignals", + "riskScore", + "riskScoreMapping", + "severity", + "severityMapping", + "threat", + "to", + "references", + "version", + "exceptionsList", + ], + "type": "object", + }, + Object { + "properties": Object { + "dataViewId": Object { + "type": "string", + }, + "eventCategoryOverride": Object { + "type": "string", + }, + "filters": Object { + "items": Object {}, + "type": "array", + }, + "index": Object { + "items": Object { + "type": "string", + }, + "type": "array", + }, + "language": Object { + "const": "eql", + "type": "string", + }, + "query": Object { + "type": "string", + }, + "tiebreakerField": Object { + "type": "string", + }, + "timestampField": Object { + "type": "string", + }, + "type": Object { + "const": "eql", + "type": "string", + }, + }, + "required": Array [ + "type", + "language", + "query", + ], + "type": "object", + }, + ], +} +`; + +exports[`Serverless upgrade and rollback checks detect param changes to review for: siem.indicatorRule 1`] = ` +Object { + "$schema": "http://json-schema.org/draft-07/schema#", + "allOf": Array [ + Object { + "properties": Object { + "author": Object { + "items": Object { + "type": "string", + }, + "type": "array", + }, + "buildingBlockType": Object { + "type": "string", + }, + "description": Object { + "minLength": 1, + "type": "string", + }, + "exceptionsList": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "id": Object { + "$ref": "#/allOf/0/properties/investigationFields/anyOf/0/properties/field_names/items", + }, + "list_id": Object { + "$ref": "#/allOf/0/properties/investigationFields/anyOf/0/properties/field_names/items", + }, + "namespace_type": Object { + "enum": Array [ + "agnostic", + "single", + ], + "type": "string", + }, + "type": Object { + "enum": Array [ + "detection", + "rule_default", + "endpoint", + "endpoint_trusted_apps", + "endpoint_events", + "endpoint_host_isolation_exceptions", + "endpoint_blocklists", + ], + "type": "string", + }, + }, + "required": Array [ + "id", + "list_id", + "type", + "namespace_type", + ], + "type": "object", + }, + "type": "array", + }, + "falsePositives": Object { + "items": Object { + "type": "string", + }, + "type": "array", + }, + "from": Object { + "type": "string", + }, + "immutable": Object { + "type": "boolean", + }, + "investigationFields": Object { + "anyOf": Array [ + Object { + "additionalProperties": false, + "properties": Object { + "field_names": Object { + "items": Object { + "minLength": 1, + "pattern": "^(?! *$).+$", + "type": "string", + }, + "minItems": 1, + "type": "array", + }, + }, + "required": Array [ + "field_names", + ], + "type": "object", + }, + Object { + "items": Object { + "type": "string", + }, + "type": "array", + }, + ], + }, + "license": Object { + "type": "string", + }, + "maxSignals": Object { + "minimum": 1, + "type": "integer", + }, + "meta": Object { + "additionalProperties": Object {}, + "properties": Object {}, + "type": "object", + }, + "namespace": Object { + "type": "string", + }, + "note": Object { + "type": "string", + }, + "outputIndex": Object { + "type": "string", + }, + "references": Object { + "items": Object { + "type": "string", + }, + "type": "array", + }, + "relatedIntegrations": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "integration": Object { + "$ref": "#/allOf/0/properties/investigationFields/anyOf/0/properties/field_names/items", + }, + "package": Object { + "$ref": "#/allOf/0/properties/investigationFields/anyOf/0/properties/field_names/items", + }, + "version": Object { + "$ref": "#/allOf/0/properties/investigationFields/anyOf/0/properties/field_names/items", + }, + }, + "required": Array [ + "package", + "version", + ], + "type": "object", + }, + "type": "array", + }, + "requiredFields": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "ecs": Object { + "type": "boolean", + }, + "name": Object { + "$ref": "#/allOf/0/properties/investigationFields/anyOf/0/properties/field_names/items", + }, + "type": Object { + "$ref": "#/allOf/0/properties/investigationFields/anyOf/0/properties/field_names/items", + }, + }, + "required": Array [ + "name", + "type", + "ecs", + ], + "type": "object", + }, + "type": "array", + }, + "riskScore": Object { + "maximum": 100, + "minimum": 0, + "type": "integer", + }, + "riskScoreMapping": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "field": Object { + "type": "string", + }, + "operator": Object { + "const": "equals", + "type": "string", + }, + "risk_score": Object { + "$ref": "#/allOf/0/properties/riskScore", + }, + "value": Object { + "type": "string", + }, + }, + "required": Array [ + "field", + "operator", + "value", + ], + "type": "object", + }, + "type": "array", + }, + "ruleId": Object { + "type": "string", + }, + "ruleNameOverride": Object { + "type": "string", + }, + "setup": Object { + "type": "string", + }, + "severity": Object { + "enum": Array [ + "low", + "medium", + "high", + "critical", + ], + "type": "string", + }, + "severityMapping": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "field": Object { + "type": "string", + }, + "operator": Object { + "const": "equals", + "type": "string", + }, + "severity": Object { + "$ref": "#/allOf/0/properties/severity", + }, + "value": Object { + "type": "string", + }, + }, + "required": Array [ + "field", + "operator", + "severity", + "value", + ], + "type": "object", + }, + "type": "array", + }, + "threat": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "framework": Object { + "type": "string", + }, + "tactic": Object { + "additionalProperties": false, + "properties": Object { + "id": Object { + "type": "string", + }, + "name": Object { + "type": "string", + }, + "reference": Object { + "type": "string", + }, + }, + "required": Array [ + "id", + "name", + "reference", + ], + "type": "object", + }, + "technique": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "id": Object { + "type": "string", + }, + "name": Object { + "type": "string", + }, + "reference": Object { + "type": "string", + }, + "subtechnique": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "id": Object { + "type": "string", + }, + "name": Object { + "type": "string", + }, + "reference": Object { + "type": "string", + }, + }, + "required": Array [ + "id", + "name", + "reference", + ], + "type": "object", + }, + "type": "array", + }, + }, + "required": Array [ + "id", + "name", + "reference", + ], + "type": "object", + }, + "type": "array", + }, + }, + "required": Array [ + "framework", + "tactic", + ], + "type": "object", + }, + "type": "array", + }, + "timelineId": Object { + "type": "string", + }, + "timelineTitle": Object { + "type": "string", + }, + "timestampOverride": Object { + "type": "string", + }, + "timestampOverrideFallbackDisabled": Object { + "type": "boolean", + }, + "to": Object { + "type": "string", + }, + "version": Object { + "minimum": 1, + "type": "integer", + }, + }, + "required": Array [ + "author", + "description", + "falsePositives", + "from", + "ruleId", + "immutable", + "outputIndex", + "maxSignals", + "riskScore", + "riskScoreMapping", + "severity", + "severityMapping", + "threat", + "to", + "references", + "version", + "exceptionsList", + ], + "type": "object", + }, + Object { + "properties": Object { + "alertSuppression": Object { + "additionalProperties": false, + "properties": Object { + "duration": Object { + "additionalProperties": false, + "properties": Object { + "unit": Object { + "enum": Array [ + "s", + "m", + "h", + ], + "type": "string", + }, + "value": Object { + "minimum": 1, + "type": "integer", + }, + }, + "required": Array [ + "value", + "unit", + ], + "type": "object", + }, + "groupBy": Object { + "items": Object { + "type": "string", + }, + "maxItems": 3, + "minItems": 1, + "type": "array", + }, + "missingFieldsStrategy": Object { + "enum": Array [ + "doNotSuppress", + "suppress", + ], + "type": "string", + }, + }, + "required": Array [ + "groupBy", + ], + "type": "object", + }, + "concurrentSearches": Object { + "minimum": 1, + "type": "integer", + }, + "dataViewId": Object { + "type": "string", + }, + "filters": Object { + "items": Object {}, + "type": "array", + }, + "index": Object { + "items": Object { + "type": "string", + }, + "type": "array", + }, + "itemsPerSearch": Object { + "minimum": 1, + "type": "integer", + }, + "language": Object { + "enum": Array [ + "kuery", + "lucene", + ], + "type": "string", + }, + "query": Object { + "type": "string", + }, + "savedId": Object { + "type": "string", + }, + "threatFilters": Object { + "$ref": "#/allOf/1/properties/filters", + }, + "threatIndex": Object { + "items": Object { + "type": "string", + }, + "type": "array", + }, + "threatIndicatorPath": Object { + "type": "string", + }, + "threatLanguage": Object { + "$ref": "#/allOf/1/properties/language", + }, + "threatMapping": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "entries": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "field": Object { + "$ref": "#/allOf/0/properties/investigationFields/anyOf/0/properties/field_names/items", + }, + "type": Object { + "const": "mapping", + "type": "string", + }, + "value": Object { + "$ref": "#/allOf/0/properties/investigationFields/anyOf/0/properties/field_names/items", + }, + }, + "required": Array [ + "field", + "type", + "value", + ], + "type": "object", + }, + "type": "array", + }, + }, + "required": Array [ + "entries", + ], + "type": "object", + }, + "minItems": 1, + "type": "array", + }, + "threatQuery": Object { + "type": "string", + }, + "type": Object { + "const": "threat_match", + "type": "string", + }, + }, + "required": Array [ + "type", + "language", + "query", + "threatQuery", + "threatMapping", + "threatIndex", + ], + "type": "object", + }, + ], +} +`; + +exports[`Serverless upgrade and rollback checks detect param changes to review for: siem.mlRule 1`] = ` +Object { + "$schema": "http://json-schema.org/draft-07/schema#", + "allOf": Array [ + Object { + "properties": Object { + "author": Object { + "items": Object { + "type": "string", + }, + "type": "array", + }, + "buildingBlockType": Object { + "type": "string", + }, + "description": Object { + "minLength": 1, + "type": "string", + }, + "exceptionsList": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "id": Object { + "$ref": "#/allOf/0/properties/investigationFields/anyOf/0/properties/field_names/items", + }, + "list_id": Object { + "$ref": "#/allOf/0/properties/investigationFields/anyOf/0/properties/field_names/items", + }, + "namespace_type": Object { + "enum": Array [ + "agnostic", + "single", + ], + "type": "string", + }, + "type": Object { + "enum": Array [ + "detection", + "rule_default", + "endpoint", + "endpoint_trusted_apps", + "endpoint_events", + "endpoint_host_isolation_exceptions", + "endpoint_blocklists", + ], + "type": "string", + }, + }, + "required": Array [ + "id", + "list_id", + "type", + "namespace_type", + ], + "type": "object", + }, + "type": "array", + }, + "falsePositives": Object { + "items": Object { + "type": "string", + }, + "type": "array", + }, + "from": Object { + "type": "string", + }, + "immutable": Object { + "type": "boolean", + }, + "investigationFields": Object { + "anyOf": Array [ + Object { + "additionalProperties": false, + "properties": Object { + "field_names": Object { + "items": Object { + "minLength": 1, + "pattern": "^(?! *$).+$", + "type": "string", + }, + "minItems": 1, + "type": "array", + }, + }, + "required": Array [ + "field_names", + ], + "type": "object", + }, + Object { + "items": Object { + "type": "string", + }, + "type": "array", + }, + ], + }, + "license": Object { + "type": "string", + }, + "maxSignals": Object { + "minimum": 1, + "type": "integer", + }, + "meta": Object { + "additionalProperties": Object {}, + "properties": Object {}, + "type": "object", + }, + "namespace": Object { + "type": "string", + }, + "note": Object { + "type": "string", + }, + "outputIndex": Object { + "type": "string", + }, + "references": Object { + "items": Object { + "type": "string", + }, + "type": "array", + }, + "relatedIntegrations": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "integration": Object { + "$ref": "#/allOf/0/properties/investigationFields/anyOf/0/properties/field_names/items", + }, + "package": Object { + "$ref": "#/allOf/0/properties/investigationFields/anyOf/0/properties/field_names/items", + }, + "version": Object { + "$ref": "#/allOf/0/properties/investigationFields/anyOf/0/properties/field_names/items", + }, + }, + "required": Array [ + "package", + "version", + ], + "type": "object", + }, + "type": "array", + }, + "requiredFields": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "ecs": Object { + "type": "boolean", + }, + "name": Object { + "$ref": "#/allOf/0/properties/investigationFields/anyOf/0/properties/field_names/items", + }, + "type": Object { + "$ref": "#/allOf/0/properties/investigationFields/anyOf/0/properties/field_names/items", + }, + }, + "required": Array [ + "name", + "type", + "ecs", + ], + "type": "object", + }, + "type": "array", + }, + "riskScore": Object { + "maximum": 100, + "minimum": 0, + "type": "integer", + }, + "riskScoreMapping": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "field": Object { + "type": "string", + }, + "operator": Object { + "const": "equals", + "type": "string", + }, + "risk_score": Object { + "$ref": "#/allOf/0/properties/riskScore", + }, + "value": Object { + "type": "string", + }, + }, + "required": Array [ + "field", + "operator", + "value", + ], + "type": "object", + }, + "type": "array", + }, + "ruleId": Object { + "type": "string", + }, + "ruleNameOverride": Object { + "type": "string", + }, + "setup": Object { + "type": "string", + }, + "severity": Object { + "enum": Array [ + "low", + "medium", + "high", + "critical", + ], + "type": "string", + }, + "severityMapping": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "field": Object { + "type": "string", + }, + "operator": Object { + "const": "equals", + "type": "string", + }, + "severity": Object { + "$ref": "#/allOf/0/properties/severity", + }, + "value": Object { + "type": "string", + }, + }, + "required": Array [ + "field", + "operator", + "severity", + "value", + ], + "type": "object", + }, + "type": "array", + }, + "threat": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "framework": Object { + "type": "string", + }, + "tactic": Object { + "additionalProperties": false, + "properties": Object { + "id": Object { + "type": "string", + }, + "name": Object { + "type": "string", + }, + "reference": Object { + "type": "string", + }, + }, + "required": Array [ + "id", + "name", + "reference", + ], + "type": "object", + }, + "technique": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "id": Object { + "type": "string", + }, + "name": Object { + "type": "string", + }, + "reference": Object { + "type": "string", + }, + "subtechnique": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "id": Object { + "type": "string", + }, + "name": Object { + "type": "string", + }, + "reference": Object { + "type": "string", + }, + }, + "required": Array [ + "id", + "name", + "reference", + ], + "type": "object", + }, + "type": "array", + }, + }, + "required": Array [ + "id", + "name", + "reference", + ], + "type": "object", + }, + "type": "array", + }, + }, + "required": Array [ + "framework", + "tactic", + ], + "type": "object", + }, + "type": "array", + }, + "timelineId": Object { + "type": "string", + }, + "timelineTitle": Object { + "type": "string", + }, + "timestampOverride": Object { + "type": "string", + }, + "timestampOverrideFallbackDisabled": Object { + "type": "boolean", + }, + "to": Object { + "type": "string", + }, + "version": Object { + "minimum": 1, + "type": "integer", + }, + }, + "required": Array [ + "author", + "description", + "falsePositives", + "from", + "ruleId", + "immutable", + "outputIndex", + "maxSignals", + "riskScore", + "riskScoreMapping", + "severity", + "severityMapping", + "threat", + "to", + "references", + "version", + "exceptionsList", + ], + "type": "object", + }, + Object { + "properties": Object { + "anomalyThreshold": Object { + "minimum": 0, + "type": "integer", + }, + "machineLearningJobId": Object { + "items": Object { + "type": "string", + }, + "type": "array", + }, + "type": Object { + "const": "machine_learning", + "type": "string", + }, + }, + "required": Array [ + "type", + "anomalyThreshold", + "machineLearningJobId", + ], + "type": "object", + }, + ], +} +`; + +exports[`Serverless upgrade and rollback checks detect param changes to review for: siem.newTermsRule 1`] = ` +Object { + "$schema": "http://json-schema.org/draft-07/schema#", + "allOf": Array [ + Object { + "properties": Object { + "author": Object { + "items": Object { + "type": "string", + }, + "type": "array", + }, + "buildingBlockType": Object { + "type": "string", + }, + "description": Object { + "minLength": 1, + "type": "string", + }, + "exceptionsList": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "id": Object { + "$ref": "#/allOf/0/properties/investigationFields/anyOf/0/properties/field_names/items", + }, + "list_id": Object { + "$ref": "#/allOf/0/properties/investigationFields/anyOf/0/properties/field_names/items", + }, + "namespace_type": Object { + "enum": Array [ + "agnostic", + "single", + ], + "type": "string", + }, + "type": Object { + "enum": Array [ + "detection", + "rule_default", + "endpoint", + "endpoint_trusted_apps", + "endpoint_events", + "endpoint_host_isolation_exceptions", + "endpoint_blocklists", + ], + "type": "string", + }, + }, + "required": Array [ + "id", + "list_id", + "type", + "namespace_type", + ], + "type": "object", + }, + "type": "array", + }, + "falsePositives": Object { + "items": Object { + "type": "string", + }, + "type": "array", + }, + "from": Object { + "type": "string", + }, + "immutable": Object { + "type": "boolean", + }, + "investigationFields": Object { + "anyOf": Array [ + Object { + "additionalProperties": false, + "properties": Object { + "field_names": Object { + "items": Object { + "minLength": 1, + "pattern": "^(?! *$).+$", + "type": "string", + }, + "minItems": 1, + "type": "array", + }, + }, + "required": Array [ + "field_names", + ], + "type": "object", + }, + Object { + "items": Object { + "type": "string", + }, + "type": "array", + }, + ], + }, + "license": Object { + "type": "string", + }, + "maxSignals": Object { + "minimum": 1, + "type": "integer", + }, + "meta": Object { + "additionalProperties": Object {}, + "properties": Object {}, + "type": "object", + }, + "namespace": Object { + "type": "string", + }, + "note": Object { + "type": "string", + }, + "outputIndex": Object { + "type": "string", + }, + "references": Object { + "items": Object { + "type": "string", + }, + "type": "array", + }, + "relatedIntegrations": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "integration": Object { + "$ref": "#/allOf/0/properties/investigationFields/anyOf/0/properties/field_names/items", + }, + "package": Object { + "$ref": "#/allOf/0/properties/investigationFields/anyOf/0/properties/field_names/items", + }, + "version": Object { + "$ref": "#/allOf/0/properties/investigationFields/anyOf/0/properties/field_names/items", + }, + }, + "required": Array [ + "package", + "version", + ], + "type": "object", + }, + "type": "array", + }, + "requiredFields": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "ecs": Object { + "type": "boolean", + }, + "name": Object { + "$ref": "#/allOf/0/properties/investigationFields/anyOf/0/properties/field_names/items", + }, + "type": Object { + "$ref": "#/allOf/0/properties/investigationFields/anyOf/0/properties/field_names/items", + }, + }, + "required": Array [ + "name", + "type", + "ecs", + ], + "type": "object", + }, + "type": "array", + }, + "riskScore": Object { + "maximum": 100, + "minimum": 0, + "type": "integer", + }, + "riskScoreMapping": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "field": Object { + "type": "string", + }, + "operator": Object { + "const": "equals", + "type": "string", + }, + "risk_score": Object { + "$ref": "#/allOf/0/properties/riskScore", + }, + "value": Object { + "type": "string", + }, + }, + "required": Array [ + "field", + "operator", + "value", + ], + "type": "object", + }, + "type": "array", + }, + "ruleId": Object { + "type": "string", + }, + "ruleNameOverride": Object { + "type": "string", + }, + "setup": Object { + "type": "string", + }, + "severity": Object { + "enum": Array [ + "low", + "medium", + "high", + "critical", + ], + "type": "string", + }, + "severityMapping": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "field": Object { + "type": "string", + }, + "operator": Object { + "const": "equals", + "type": "string", + }, + "severity": Object { + "$ref": "#/allOf/0/properties/severity", + }, + "value": Object { + "type": "string", + }, + }, + "required": Array [ + "field", + "operator", + "severity", + "value", + ], + "type": "object", + }, + "type": "array", + }, + "threat": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "framework": Object { + "type": "string", + }, + "tactic": Object { + "additionalProperties": false, + "properties": Object { + "id": Object { + "type": "string", + }, + "name": Object { + "type": "string", + }, + "reference": Object { + "type": "string", + }, + }, + "required": Array [ + "id", + "name", + "reference", + ], + "type": "object", + }, + "technique": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "id": Object { + "type": "string", + }, + "name": Object { + "type": "string", + }, + "reference": Object { + "type": "string", + }, + "subtechnique": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "id": Object { + "type": "string", + }, + "name": Object { + "type": "string", + }, + "reference": Object { + "type": "string", + }, + }, + "required": Array [ + "id", + "name", + "reference", + ], + "type": "object", + }, + "type": "array", + }, + }, + "required": Array [ + "id", + "name", + "reference", + ], + "type": "object", + }, + "type": "array", + }, + }, + "required": Array [ + "framework", + "tactic", + ], + "type": "object", + }, + "type": "array", + }, + "timelineId": Object { + "type": "string", + }, + "timelineTitle": Object { + "type": "string", + }, + "timestampOverride": Object { + "type": "string", + }, + "timestampOverrideFallbackDisabled": Object { + "type": "boolean", + }, + "to": Object { + "type": "string", + }, + "version": Object { + "minimum": 1, + "type": "integer", + }, + }, + "required": Array [ + "author", + "description", + "falsePositives", + "from", + "ruleId", + "immutable", + "outputIndex", + "maxSignals", + "riskScore", + "riskScoreMapping", + "severity", + "severityMapping", + "threat", + "to", + "references", + "version", + "exceptionsList", + ], + "type": "object", + }, + Object { + "properties": Object { + "dataViewId": Object { + "type": "string", + }, + "filters": Object { + "items": Object {}, + "type": "array", + }, + "historyWindowStart": Object { + "$ref": "#/allOf/0/properties/investigationFields/anyOf/0/properties/field_names/items", + }, + "index": Object { + "items": Object { + "type": "string", + }, + "type": "array", + }, + "language": Object { + "enum": Array [ + "kuery", + "lucene", + ], + "type": "string", + }, + "newTermsFields": Object { + "items": Object { + "type": "string", + }, + "maxItems": 3, + "minItems": 1, + "type": "array", + }, + "query": Object { + "type": "string", + }, + "type": Object { + "const": "new_terms", + "type": "string", + }, + }, + "required": Array [ + "type", + "query", + "newTermsFields", + "historyWindowStart", + "language", + ], + "type": "object", + }, + ], +} +`; + +exports[`Serverless upgrade and rollback checks detect param changes to review for: siem.notifications 1`] = ` +Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "ruleAlertId": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", +} +`; + +exports[`Serverless upgrade and rollback checks detect param changes to review for: siem.queryRule 1`] = ` +Object { + "$schema": "http://json-schema.org/draft-07/schema#", + "allOf": Array [ + Object { + "properties": Object { + "author": Object { + "items": Object { + "type": "string", + }, + "type": "array", + }, + "buildingBlockType": Object { + "type": "string", + }, + "description": Object { + "minLength": 1, + "type": "string", + }, + "exceptionsList": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "id": Object { + "$ref": "#/allOf/0/properties/investigationFields/anyOf/0/properties/field_names/items", + }, + "list_id": Object { + "$ref": "#/allOf/0/properties/investigationFields/anyOf/0/properties/field_names/items", + }, + "namespace_type": Object { + "enum": Array [ + "agnostic", + "single", + ], + "type": "string", + }, + "type": Object { + "enum": Array [ + "detection", + "rule_default", + "endpoint", + "endpoint_trusted_apps", + "endpoint_events", + "endpoint_host_isolation_exceptions", + "endpoint_blocklists", + ], + "type": "string", + }, + }, + "required": Array [ + "id", + "list_id", + "type", + "namespace_type", + ], + "type": "object", + }, + "type": "array", + }, + "falsePositives": Object { + "items": Object { + "type": "string", + }, + "type": "array", + }, + "from": Object { + "type": "string", + }, + "immutable": Object { + "type": "boolean", + }, + "investigationFields": Object { + "anyOf": Array [ + Object { + "additionalProperties": false, + "properties": Object { + "field_names": Object { + "items": Object { + "minLength": 1, + "pattern": "^(?! *$).+$", + "type": "string", + }, + "minItems": 1, + "type": "array", + }, + }, + "required": Array [ + "field_names", + ], + "type": "object", + }, + Object { + "items": Object { + "type": "string", + }, + "type": "array", + }, + ], + }, + "license": Object { + "type": "string", + }, + "maxSignals": Object { + "minimum": 1, + "type": "integer", + }, + "meta": Object { + "additionalProperties": Object {}, + "properties": Object {}, + "type": "object", + }, + "namespace": Object { + "type": "string", + }, + "note": Object { + "type": "string", + }, + "outputIndex": Object { + "type": "string", + }, + "references": Object { + "items": Object { + "type": "string", + }, + "type": "array", + }, + "relatedIntegrations": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "integration": Object { + "$ref": "#/allOf/0/properties/investigationFields/anyOf/0/properties/field_names/items", + }, + "package": Object { + "$ref": "#/allOf/0/properties/investigationFields/anyOf/0/properties/field_names/items", + }, + "version": Object { + "$ref": "#/allOf/0/properties/investigationFields/anyOf/0/properties/field_names/items", + }, + }, + "required": Array [ + "package", + "version", + ], + "type": "object", + }, + "type": "array", + }, + "requiredFields": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "ecs": Object { + "type": "boolean", + }, + "name": Object { + "$ref": "#/allOf/0/properties/investigationFields/anyOf/0/properties/field_names/items", + }, + "type": Object { + "$ref": "#/allOf/0/properties/investigationFields/anyOf/0/properties/field_names/items", + }, + }, + "required": Array [ + "name", + "type", + "ecs", + ], + "type": "object", + }, + "type": "array", + }, + "riskScore": Object { + "maximum": 100, + "minimum": 0, + "type": "integer", + }, + "riskScoreMapping": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "field": Object { + "type": "string", + }, + "operator": Object { + "const": "equals", + "type": "string", + }, + "risk_score": Object { + "$ref": "#/allOf/0/properties/riskScore", + }, + "value": Object { + "type": "string", + }, + }, + "required": Array [ + "field", + "operator", + "value", + ], + "type": "object", + }, + "type": "array", + }, + "ruleId": Object { + "type": "string", + }, + "ruleNameOverride": Object { + "type": "string", + }, + "setup": Object { + "type": "string", + }, + "severity": Object { + "enum": Array [ + "low", + "medium", + "high", + "critical", + ], + "type": "string", + }, + "severityMapping": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "field": Object { + "type": "string", + }, + "operator": Object { + "const": "equals", + "type": "string", + }, + "severity": Object { + "$ref": "#/allOf/0/properties/severity", + }, + "value": Object { + "type": "string", + }, + }, + "required": Array [ + "field", + "operator", + "severity", + "value", + ], + "type": "object", + }, + "type": "array", + }, + "threat": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "framework": Object { + "type": "string", + }, + "tactic": Object { + "additionalProperties": false, + "properties": Object { + "id": Object { + "type": "string", + }, + "name": Object { + "type": "string", + }, + "reference": Object { + "type": "string", + }, + }, + "required": Array [ + "id", + "name", + "reference", + ], + "type": "object", + }, + "technique": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "id": Object { + "type": "string", + }, + "name": Object { + "type": "string", + }, + "reference": Object { + "type": "string", + }, + "subtechnique": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "id": Object { + "type": "string", + }, + "name": Object { + "type": "string", + }, + "reference": Object { + "type": "string", + }, + }, + "required": Array [ + "id", + "name", + "reference", + ], + "type": "object", + }, + "type": "array", + }, + }, + "required": Array [ + "id", + "name", + "reference", + ], + "type": "object", + }, + "type": "array", + }, + }, + "required": Array [ + "framework", + "tactic", + ], + "type": "object", + }, + "type": "array", + }, + "timelineId": Object { + "type": "string", + }, + "timelineTitle": Object { + "type": "string", + }, + "timestampOverride": Object { + "type": "string", + }, + "timestampOverrideFallbackDisabled": Object { + "type": "boolean", + }, + "to": Object { + "type": "string", + }, + "version": Object { + "minimum": 1, + "type": "integer", + }, + }, + "required": Array [ + "author", + "description", + "falsePositives", + "from", + "ruleId", + "immutable", + "outputIndex", + "maxSignals", + "riskScore", + "riskScoreMapping", + "severity", + "severityMapping", + "threat", + "to", + "references", + "version", + "exceptionsList", + ], + "type": "object", + }, + Object { + "anyOf": Array [ + Object { + "additionalProperties": false, + "properties": Object { + "alertSuppression": Object { + "additionalProperties": false, + "properties": Object { + "duration": Object { + "additionalProperties": false, + "properties": Object { + "unit": Object { + "enum": Array [ + "s", + "m", + "h", + ], + "type": "string", + }, + "value": Object { + "minimum": 1, + "type": "integer", + }, + }, + "required": Array [ + "value", + "unit", + ], + "type": "object", + }, + "groupBy": Object { + "items": Object { + "type": "string", + }, + "maxItems": 3, + "minItems": 1, + "type": "array", + }, + "missingFieldsStrategy": Object { + "enum": Array [ + "doNotSuppress", + "suppress", + ], + "type": "string", + }, + }, + "required": Array [ + "groupBy", + ], + "type": "object", + }, + "dataViewId": Object { + "type": "string", + }, + "filters": Object { + "items": Object {}, + "type": "array", + }, + "index": Object { + "items": Object { + "type": "string", + }, + "type": "array", + }, + "language": Object { + "enum": Array [ + "kuery", + "lucene", + ], + "type": "string", + }, + "query": Object { + "type": "string", + }, + "responseActions": Object { + "items": Object { + "anyOf": Array [ + Object { + "additionalProperties": false, + "properties": Object { + "actionTypeId": Object { + "const": ".osquery", + "type": "string", + }, + "params": Object { + "additionalProperties": false, + "properties": Object { + "ecsMapping": Object { + "additionalProperties": Object { + "additionalProperties": false, + "properties": Object { + "field": Object { + "type": "string", + }, + "value": Object { + "anyOf": Array [ + Object { + "type": "string", + }, + Object { + "items": Object { + "type": "string", + }, + "type": "array", + }, + ], + }, + }, + "type": "object", + }, + "properties": Object {}, + "type": "object", + }, + "packId": Object { + "type": "string", + }, + "queries": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "ecs_mapping": Object { + "$ref": "#/allOf/1/anyOf/0/properties/responseActions/items/anyOf/0/properties/params/properties/ecsMapping", + }, + "id": Object { + "type": "string", + }, + "platform": Object { + "type": "string", + }, + "query": Object { + "type": "string", + }, + "removed": Object { + "type": "boolean", + }, + "snapshot": Object { + "type": "boolean", + }, + "version": Object { + "type": "string", + }, + }, + "required": Array [ + "id", + "query", + ], + "type": "object", + }, + "type": "array", + }, + "query": Object { + "type": "string", + }, + "savedQueryId": Object { + "type": "string", + }, + "timeout": Object { + "type": "number", + }, + }, + "type": "object", + }, + }, + "required": Array [ + "actionTypeId", + "params", + ], + "type": "object", + }, + Object { + "additionalProperties": false, + "properties": Object { + "actionTypeId": Object { + "const": ".endpoint", + "type": "string", + }, + "params": Object { + "anyOf": Array [ + Object { + "additionalProperties": false, + "properties": Object { + "command": Object { + "const": "isolate", + "type": "string", + }, + "comment": Object { + "type": "string", + }, + }, + "required": Array [ + "command", + ], + "type": "object", + }, + Object { + "additionalProperties": false, + "properties": Object { + "command": Object { + "enum": Array [ + "kill-process", + "suspend-process", + ], + "type": "string", + }, + "comment": Object { + "type": "string", + }, + "config": Object { + "additionalProperties": false, + "properties": Object { + "field": Object { + "type": "string", + }, + "overwrite": Object { + "default": true, + "type": "boolean", + }, + }, + "required": Array [ + "field", + ], + "type": "object", + }, + }, + "required": Array [ + "command", + "config", + ], + "type": "object", + }, + ], + }, + }, + "required": Array [ + "actionTypeId", + "params", + ], + "type": "object", + }, + ], + }, + "type": "array", + }, + "savedId": Object { + "type": "string", + }, + "type": Object { + "const": "query", + "type": "string", + }, + }, + "required": Array [ + "type", + "language", + "query", + ], + "type": "object", + }, + Object { + "additionalProperties": false, + "properties": Object { + "alertSuppression": Object { + "$ref": "#/allOf/1/anyOf/0/properties/alertSuppression", + }, + "dataViewId": Object { + "$ref": "#/allOf/1/anyOf/0/properties/dataViewId", + }, + "filters": Object { + "$ref": "#/allOf/1/anyOf/0/properties/filters", + }, + "index": Object { + "$ref": "#/allOf/1/anyOf/0/properties/index", + }, + "language": Object { + "$ref": "#/allOf/1/anyOf/0/properties/language", + }, + "query": Object { + "$ref": "#/allOf/1/anyOf/0/properties/query", + }, + "responseActions": Object { + "items": Object { + "$ref": "#/allOf/1/anyOf/0/properties/responseActions/items", + }, + "type": "array", + }, + "savedId": Object { + "$ref": "#/allOf/1/anyOf/0/properties/savedId", + }, + "type": Object { + "const": "saved_query", + "type": "string", + }, + }, + "required": Array [ + "type", + "language", + "savedId", + ], + "type": "object", + }, + ], + }, + ], +} +`; + +exports[`Serverless upgrade and rollback checks detect param changes to review for: siem.savedQueryRule 1`] = ` +Object { + "$schema": "http://json-schema.org/draft-07/schema#", + "allOf": Array [ + Object { + "properties": Object { + "author": Object { + "items": Object { + "type": "string", + }, + "type": "array", + }, + "buildingBlockType": Object { + "type": "string", + }, + "description": Object { + "minLength": 1, + "type": "string", + }, + "exceptionsList": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "id": Object { + "$ref": "#/allOf/0/properties/investigationFields/anyOf/0/properties/field_names/items", + }, + "list_id": Object { + "$ref": "#/allOf/0/properties/investigationFields/anyOf/0/properties/field_names/items", + }, + "namespace_type": Object { + "enum": Array [ + "agnostic", + "single", + ], + "type": "string", + }, + "type": Object { + "enum": Array [ + "detection", + "rule_default", + "endpoint", + "endpoint_trusted_apps", + "endpoint_events", + "endpoint_host_isolation_exceptions", + "endpoint_blocklists", + ], + "type": "string", + }, + }, + "required": Array [ + "id", + "list_id", + "type", + "namespace_type", + ], + "type": "object", + }, + "type": "array", + }, + "falsePositives": Object { + "items": Object { + "type": "string", + }, + "type": "array", + }, + "from": Object { + "type": "string", + }, + "immutable": Object { + "type": "boolean", + }, + "investigationFields": Object { + "anyOf": Array [ + Object { + "additionalProperties": false, + "properties": Object { + "field_names": Object { + "items": Object { + "minLength": 1, + "pattern": "^(?! *$).+$", + "type": "string", + }, + "minItems": 1, + "type": "array", + }, + }, + "required": Array [ + "field_names", + ], + "type": "object", + }, + Object { + "items": Object { + "type": "string", + }, + "type": "array", + }, + ], + }, + "license": Object { + "type": "string", + }, + "maxSignals": Object { + "minimum": 1, + "type": "integer", + }, + "meta": Object { + "additionalProperties": Object {}, + "properties": Object {}, + "type": "object", + }, + "namespace": Object { + "type": "string", + }, + "note": Object { + "type": "string", + }, + "outputIndex": Object { + "type": "string", + }, + "references": Object { + "items": Object { + "type": "string", + }, + "type": "array", + }, + "relatedIntegrations": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "integration": Object { + "$ref": "#/allOf/0/properties/investigationFields/anyOf/0/properties/field_names/items", + }, + "package": Object { + "$ref": "#/allOf/0/properties/investigationFields/anyOf/0/properties/field_names/items", + }, + "version": Object { + "$ref": "#/allOf/0/properties/investigationFields/anyOf/0/properties/field_names/items", + }, + }, + "required": Array [ + "package", + "version", + ], + "type": "object", + }, + "type": "array", + }, + "requiredFields": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "ecs": Object { + "type": "boolean", + }, + "name": Object { + "$ref": "#/allOf/0/properties/investigationFields/anyOf/0/properties/field_names/items", + }, + "type": Object { + "$ref": "#/allOf/0/properties/investigationFields/anyOf/0/properties/field_names/items", + }, + }, + "required": Array [ + "name", + "type", + "ecs", + ], + "type": "object", + }, + "type": "array", + }, + "riskScore": Object { + "maximum": 100, + "minimum": 0, + "type": "integer", + }, + "riskScoreMapping": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "field": Object { + "type": "string", + }, + "operator": Object { + "const": "equals", + "type": "string", + }, + "risk_score": Object { + "$ref": "#/allOf/0/properties/riskScore", + }, + "value": Object { + "type": "string", + }, + }, + "required": Array [ + "field", + "operator", + "value", + ], + "type": "object", + }, + "type": "array", + }, + "ruleId": Object { + "type": "string", + }, + "ruleNameOverride": Object { + "type": "string", + }, + "setup": Object { + "type": "string", + }, + "severity": Object { + "enum": Array [ + "low", + "medium", + "high", + "critical", + ], + "type": "string", + }, + "severityMapping": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "field": Object { + "type": "string", + }, + "operator": Object { + "const": "equals", + "type": "string", + }, + "severity": Object { + "$ref": "#/allOf/0/properties/severity", + }, + "value": Object { + "type": "string", + }, + }, + "required": Array [ + "field", + "operator", + "severity", + "value", + ], + "type": "object", + }, + "type": "array", + }, + "threat": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "framework": Object { + "type": "string", + }, + "tactic": Object { + "additionalProperties": false, + "properties": Object { + "id": Object { + "type": "string", + }, + "name": Object { + "type": "string", + }, + "reference": Object { + "type": "string", + }, + }, + "required": Array [ + "id", + "name", + "reference", + ], + "type": "object", + }, + "technique": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "id": Object { + "type": "string", + }, + "name": Object { + "type": "string", + }, + "reference": Object { + "type": "string", + }, + "subtechnique": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "id": Object { + "type": "string", + }, + "name": Object { + "type": "string", + }, + "reference": Object { + "type": "string", + }, + }, + "required": Array [ + "id", + "name", + "reference", + ], + "type": "object", + }, + "type": "array", + }, + }, + "required": Array [ + "id", + "name", + "reference", + ], + "type": "object", + }, + "type": "array", + }, + }, + "required": Array [ + "framework", + "tactic", + ], + "type": "object", + }, + "type": "array", + }, + "timelineId": Object { + "type": "string", + }, + "timelineTitle": Object { + "type": "string", + }, + "timestampOverride": Object { + "type": "string", + }, + "timestampOverrideFallbackDisabled": Object { + "type": "boolean", + }, + "to": Object { + "type": "string", + }, + "version": Object { + "minimum": 1, + "type": "integer", + }, + }, + "required": Array [ + "author", + "description", + "falsePositives", + "from", + "ruleId", + "immutable", + "outputIndex", + "maxSignals", + "riskScore", + "riskScoreMapping", + "severity", + "severityMapping", + "threat", + "to", + "references", + "version", + "exceptionsList", + ], + "type": "object", + }, + Object { + "anyOf": Array [ + Object { + "additionalProperties": false, + "properties": Object { + "alertSuppression": Object { + "additionalProperties": false, + "properties": Object { + "duration": Object { + "additionalProperties": false, + "properties": Object { + "unit": Object { + "enum": Array [ + "s", + "m", + "h", + ], + "type": "string", + }, + "value": Object { + "minimum": 1, + "type": "integer", + }, + }, + "required": Array [ + "value", + "unit", + ], + "type": "object", + }, + "groupBy": Object { + "items": Object { + "type": "string", + }, + "maxItems": 3, + "minItems": 1, + "type": "array", + }, + "missingFieldsStrategy": Object { + "enum": Array [ + "doNotSuppress", + "suppress", + ], + "type": "string", + }, + }, + "required": Array [ + "groupBy", + ], + "type": "object", + }, + "dataViewId": Object { + "type": "string", + }, + "filters": Object { + "items": Object {}, + "type": "array", + }, + "index": Object { + "items": Object { + "type": "string", + }, + "type": "array", + }, + "language": Object { + "enum": Array [ + "kuery", + "lucene", + ], + "type": "string", + }, + "query": Object { + "type": "string", + }, + "responseActions": Object { + "items": Object { + "anyOf": Array [ + Object { + "additionalProperties": false, + "properties": Object { + "actionTypeId": Object { + "const": ".osquery", + "type": "string", + }, + "params": Object { + "additionalProperties": false, + "properties": Object { + "ecsMapping": Object { + "additionalProperties": Object { + "additionalProperties": false, + "properties": Object { + "field": Object { + "type": "string", + }, + "value": Object { + "anyOf": Array [ + Object { + "type": "string", + }, + Object { + "items": Object { + "type": "string", + }, + "type": "array", + }, + ], + }, + }, + "type": "object", + }, + "properties": Object {}, + "type": "object", + }, + "packId": Object { + "type": "string", + }, + "queries": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "ecs_mapping": Object { + "$ref": "#/allOf/1/anyOf/0/properties/responseActions/items/anyOf/0/properties/params/properties/ecsMapping", + }, + "id": Object { + "type": "string", + }, + "platform": Object { + "type": "string", + }, + "query": Object { + "type": "string", + }, + "removed": Object { + "type": "boolean", + }, + "snapshot": Object { + "type": "boolean", + }, + "version": Object { + "type": "string", + }, + }, + "required": Array [ + "id", + "query", + ], + "type": "object", + }, + "type": "array", + }, + "query": Object { + "type": "string", + }, + "savedQueryId": Object { + "type": "string", + }, + "timeout": Object { + "type": "number", + }, + }, + "type": "object", + }, + }, + "required": Array [ + "actionTypeId", + "params", + ], + "type": "object", + }, + Object { + "additionalProperties": false, + "properties": Object { + "actionTypeId": Object { + "const": ".endpoint", + "type": "string", + }, + "params": Object { + "anyOf": Array [ + Object { + "additionalProperties": false, + "properties": Object { + "command": Object { + "const": "isolate", + "type": "string", + }, + "comment": Object { + "type": "string", + }, + }, + "required": Array [ + "command", + ], + "type": "object", + }, + Object { + "additionalProperties": false, + "properties": Object { + "command": Object { + "enum": Array [ + "kill-process", + "suspend-process", + ], + "type": "string", + }, + "comment": Object { + "type": "string", + }, + "config": Object { + "additionalProperties": false, + "properties": Object { + "field": Object { + "type": "string", + }, + "overwrite": Object { + "default": true, + "type": "boolean", + }, + }, + "required": Array [ + "field", + ], + "type": "object", + }, + }, + "required": Array [ + "command", + "config", + ], + "type": "object", + }, + ], + }, + }, + "required": Array [ + "actionTypeId", + "params", + ], + "type": "object", + }, + ], + }, + "type": "array", + }, + "savedId": Object { + "type": "string", + }, + "type": Object { + "const": "query", + "type": "string", + }, + }, + "required": Array [ + "type", + "language", + "query", + ], + "type": "object", + }, + Object { + "additionalProperties": false, + "properties": Object { + "alertSuppression": Object { + "$ref": "#/allOf/1/anyOf/0/properties/alertSuppression", + }, + "dataViewId": Object { + "$ref": "#/allOf/1/anyOf/0/properties/dataViewId", + }, + "filters": Object { + "$ref": "#/allOf/1/anyOf/0/properties/filters", + }, + "index": Object { + "$ref": "#/allOf/1/anyOf/0/properties/index", + }, + "language": Object { + "$ref": "#/allOf/1/anyOf/0/properties/language", + }, + "query": Object { + "$ref": "#/allOf/1/anyOf/0/properties/query", + }, + "responseActions": Object { + "items": Object { + "$ref": "#/allOf/1/anyOf/0/properties/responseActions/items", + }, + "type": "array", + }, + "savedId": Object { + "$ref": "#/allOf/1/anyOf/0/properties/savedId", + }, + "type": Object { + "const": "saved_query", + "type": "string", + }, + }, + "required": Array [ + "type", + "language", + "savedId", + ], + "type": "object", + }, + ], + }, + ], +} +`; + +exports[`Serverless upgrade and rollback checks detect param changes to review for: siem.thresholdRule 1`] = ` +Object { + "$schema": "http://json-schema.org/draft-07/schema#", + "allOf": Array [ + Object { + "properties": Object { + "author": Object { + "items": Object { + "type": "string", + }, + "type": "array", + }, + "buildingBlockType": Object { + "type": "string", + }, + "description": Object { + "minLength": 1, + "type": "string", + }, + "exceptionsList": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "id": Object { + "$ref": "#/allOf/0/properties/investigationFields/anyOf/0/properties/field_names/items", + }, + "list_id": Object { + "$ref": "#/allOf/0/properties/investigationFields/anyOf/0/properties/field_names/items", + }, + "namespace_type": Object { + "enum": Array [ + "agnostic", + "single", + ], + "type": "string", + }, + "type": Object { + "enum": Array [ + "detection", + "rule_default", + "endpoint", + "endpoint_trusted_apps", + "endpoint_events", + "endpoint_host_isolation_exceptions", + "endpoint_blocklists", + ], + "type": "string", + }, + }, + "required": Array [ + "id", + "list_id", + "type", + "namespace_type", + ], + "type": "object", + }, + "type": "array", + }, + "falsePositives": Object { + "items": Object { + "type": "string", + }, + "type": "array", + }, + "from": Object { + "type": "string", + }, + "immutable": Object { + "type": "boolean", + }, + "investigationFields": Object { + "anyOf": Array [ + Object { + "additionalProperties": false, + "properties": Object { + "field_names": Object { + "items": Object { + "minLength": 1, + "pattern": "^(?! *$).+$", + "type": "string", + }, + "minItems": 1, + "type": "array", + }, + }, + "required": Array [ + "field_names", + ], + "type": "object", + }, + Object { + "items": Object { + "type": "string", + }, + "type": "array", + }, + ], + }, + "license": Object { + "type": "string", + }, + "maxSignals": Object { + "minimum": 1, + "type": "integer", + }, + "meta": Object { + "additionalProperties": Object {}, + "properties": Object {}, + "type": "object", + }, + "namespace": Object { + "type": "string", + }, + "note": Object { + "type": "string", + }, + "outputIndex": Object { + "type": "string", + }, + "references": Object { + "items": Object { + "type": "string", + }, + "type": "array", + }, + "relatedIntegrations": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "integration": Object { + "$ref": "#/allOf/0/properties/investigationFields/anyOf/0/properties/field_names/items", + }, + "package": Object { + "$ref": "#/allOf/0/properties/investigationFields/anyOf/0/properties/field_names/items", + }, + "version": Object { + "$ref": "#/allOf/0/properties/investigationFields/anyOf/0/properties/field_names/items", + }, + }, + "required": Array [ + "package", + "version", + ], + "type": "object", + }, + "type": "array", + }, + "requiredFields": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "ecs": Object { + "type": "boolean", + }, + "name": Object { + "$ref": "#/allOf/0/properties/investigationFields/anyOf/0/properties/field_names/items", + }, + "type": Object { + "$ref": "#/allOf/0/properties/investigationFields/anyOf/0/properties/field_names/items", + }, + }, + "required": Array [ + "name", + "type", + "ecs", + ], + "type": "object", + }, + "type": "array", + }, + "riskScore": Object { + "maximum": 100, + "minimum": 0, + "type": "integer", + }, + "riskScoreMapping": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "field": Object { + "type": "string", + }, + "operator": Object { + "const": "equals", + "type": "string", + }, + "risk_score": Object { + "$ref": "#/allOf/0/properties/riskScore", + }, + "value": Object { + "type": "string", + }, + }, + "required": Array [ + "field", + "operator", + "value", + ], + "type": "object", + }, + "type": "array", + }, + "ruleId": Object { + "type": "string", + }, + "ruleNameOverride": Object { + "type": "string", + }, + "setup": Object { + "type": "string", + }, + "severity": Object { + "enum": Array [ + "low", + "medium", + "high", + "critical", + ], + "type": "string", + }, + "severityMapping": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "field": Object { + "type": "string", + }, + "operator": Object { + "const": "equals", + "type": "string", + }, + "severity": Object { + "$ref": "#/allOf/0/properties/severity", + }, + "value": Object { + "type": "string", + }, + }, + "required": Array [ + "field", + "operator", + "severity", + "value", + ], + "type": "object", + }, + "type": "array", + }, + "threat": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "framework": Object { + "type": "string", + }, + "tactic": Object { + "additionalProperties": false, + "properties": Object { + "id": Object { + "type": "string", + }, + "name": Object { + "type": "string", + }, + "reference": Object { + "type": "string", + }, + }, + "required": Array [ + "id", + "name", + "reference", + ], + "type": "object", + }, + "technique": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "id": Object { + "type": "string", + }, + "name": Object { + "type": "string", + }, + "reference": Object { + "type": "string", + }, + "subtechnique": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "id": Object { + "type": "string", + }, + "name": Object { + "type": "string", + }, + "reference": Object { + "type": "string", + }, + }, + "required": Array [ + "id", + "name", + "reference", + ], + "type": "object", + }, + "type": "array", + }, + }, + "required": Array [ + "id", + "name", + "reference", + ], + "type": "object", + }, + "type": "array", + }, + }, + "required": Array [ + "framework", + "tactic", + ], + "type": "object", + }, + "type": "array", + }, + "timelineId": Object { + "type": "string", + }, + "timelineTitle": Object { + "type": "string", + }, + "timestampOverride": Object { + "type": "string", + }, + "timestampOverrideFallbackDisabled": Object { + "type": "boolean", + }, + "to": Object { + "type": "string", + }, + "version": Object { + "minimum": 1, + "type": "integer", + }, + }, + "required": Array [ + "author", + "description", + "falsePositives", + "from", + "ruleId", + "immutable", + "outputIndex", + "maxSignals", + "riskScore", + "riskScoreMapping", + "severity", + "severityMapping", + "threat", + "to", + "references", + "version", + "exceptionsList", + ], + "type": "object", + }, + Object { + "properties": Object { + "alertSuppression": Object { + "additionalProperties": false, + "properties": Object { + "duration": Object { + "additionalProperties": false, + "properties": Object { + "unit": Object { + "enum": Array [ + "s", + "m", + "h", + ], + "type": "string", + }, + "value": Object { + "minimum": 1, + "type": "integer", + }, + }, + "required": Array [ + "value", + "unit", + ], + "type": "object", + }, + }, + "required": Array [ + "duration", + ], + "type": "object", + }, + "dataViewId": Object { + "type": "string", + }, + "filters": Object { + "items": Object {}, + "type": "array", + }, + "index": Object { + "items": Object { + "type": "string", + }, + "type": "array", + }, + "language": Object { + "enum": Array [ + "kuery", + "lucene", + ], + "type": "string", + }, + "query": Object { + "type": "string", + }, + "savedId": Object { + "type": "string", + }, + "threshold": Object { + "additionalProperties": false, + "properties": Object { + "cardinality": Object { + "items": Object { + "additionalProperties": false, + "properties": Object { + "field": Object { + "type": "string", + }, + "value": Object { + "minimum": 0, + "type": "integer", + }, + }, + "required": Array [ + "field", + "value", + ], + "type": "object", + }, + "type": "array", + }, + "field": Object { + "items": Object { + "type": "string", + }, + "type": "array", + }, + "value": Object { + "minimum": 1, + "type": "integer", + }, + }, + "required": Array [ + "field", + "value", + ], + "type": "object", + }, + "type": Object { + "const": "threshold", + "type": "string", + }, + }, + "required": Array [ + "type", + "language", + "query", + "threshold", + ], + "type": "object", + }, + ], +} +`; + +exports[`Serverless upgrade and rollback checks detect param changes to review for: slo.rules.burnRate 1`] = ` +Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "sloId": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "windows": Object { + "flags": Object { + "error": [Function], + }, + "items": Array [ + Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "actionGroup": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "burnRateThreshold": Object { + "flags": Object { + "error": [Function], + }, + "type": "number", + }, + "id": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "longWindow": Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "unit": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "value": Object { + "flags": Object { + "error": [Function], + }, + "type": "number", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", + }, + "maxBurnRateThreshold": Object { + "flags": Object { + "default": null, + "error": [Function], + "presence": "optional", + }, + "matches": Array [ + Object { + "schema": Object { + "flags": Object { + "error": [Function], + }, + "type": "number", + }, + }, + Object { + "schema": Object { + "allow": Array [ + null, + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + ], + "type": "alternatives", + }, + "shortWindow": Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "unit": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "value": Object { + "flags": Object { + "error": [Function], + }, + "type": "number", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", + }, + ], + "type": "array", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", +} +`; + +exports[`Serverless upgrade and rollback checks detect param changes to review for: transform_health 1`] = ` +Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "excludeTransforms": Object { + "flags": Object { + "default": null, + "error": [Function], + "presence": "optional", + }, + "matches": Array [ + Object { + "schema": Object { + "flags": Object { + "default": Array [], + "error": [Function], + "presence": "optional", + }, + "items": Array [ + Object { + "flags": Object { + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + ], + "type": "array", + }, + }, + Object { + "schema": Object { + "allow": Array [ + null, + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + ], + "type": "alternatives", + }, + "includeTransforms": Object { + "flags": Object { + "error": [Function], + }, + "items": Array [ + Object { + "flags": Object { + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + ], + "type": "array", + }, + "testsConfig": Object { + "flags": Object { + "default": null, + "error": [Function], + "presence": "optional", + }, + "matches": Array [ + Object { + "schema": Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "errorMessages": Object { + "flags": Object { + "default": null, + "error": [Function], + "presence": "optional", + }, + "matches": Array [ + Object { + "schema": Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "enabled": Object { + "flags": Object { + "default": false, + "error": [Function], + "presence": "optional", + }, + "type": "boolean", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", + }, + }, + Object { + "schema": Object { + "allow": Array [ + null, + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + ], + "type": "alternatives", + }, + "healthCheck": Object { + "flags": Object { + "default": null, + "error": [Function], + "presence": "optional", + }, + "matches": Array [ + Object { + "schema": Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "enabled": Object { + "flags": Object { + "default": true, + "error": [Function], + "presence": "optional", + }, + "type": "boolean", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", + }, + }, + Object { + "schema": Object { + "allow": Array [ + null, + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + ], + "type": "alternatives", + }, + "notStarted": Object { + "flags": Object { + "default": null, + "error": [Function], + "presence": "optional", + }, + "matches": Array [ + Object { + "schema": Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "enabled": Object { + "flags": Object { + "default": true, + "error": [Function], + "presence": "optional", + }, + "type": "boolean", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", + }, + }, + Object { + "schema": Object { + "allow": Array [ + null, + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + ], + "type": "alternatives", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", + }, + }, + Object { + "schema": Object { + "allow": Array [ + null, + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + ], + "type": "alternatives", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", +} +`; + +exports[`Serverless upgrade and rollback checks detect param changes to review for: xpack.ml.anomaly_detection_alert 1`] = ` +Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "includeInterim": Object { + "flags": Object { + "default": true, + "error": [Function], + "presence": "optional", + }, + "type": "boolean", + }, + "jobSelection": Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "groupIds": Object { + "flags": Object { + "default": Array [], + "error": [Function], + "presence": "optional", + }, + "items": Array [ + Object { + "flags": Object { + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + ], + "type": "array", + }, + "jobIds": Object { + "flags": Object { + "default": Array [], + "error": [Function], + "presence": "optional", + }, + "items": Array [ + Object { + "flags": Object { + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + ], + "type": "array", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "object", + }, + "lookbackInterval": Object { + "flags": Object { + "default": null, + "error": [Function], + "presence": "optional", + }, + "matches": Array [ + Object { + "schema": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + }, + Object { + "schema": Object { + "allow": Array [ + null, + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + ], + "type": "alternatives", + }, + "resultType": Object { + "flags": Object { + "error": [Function], + }, + "matches": Array [ + Object { + "schema": Object { + "allow": Array [ + "record", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + Object { + "schema": Object { + "allow": Array [ + "bucket", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + Object { + "schema": Object { + "allow": Array [ + "influencer", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + ], + "type": "alternatives", + }, + "severity": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "limit": 0, + }, + "name": "min", + }, + Object { + "args": Object { + "limit": 100, + }, + "name": "max", + }, + ], + "type": "number", + }, + "topNBuckets": Object { + "flags": Object { + "default": null, + "error": [Function], + "presence": "optional", + }, + "matches": Array [ + Object { + "schema": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "limit": 1, + }, + "name": "min", + }, + ], + "type": "number", + }, + }, + Object { + "schema": Object { + "allow": Array [ + null, + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + ], + "type": "alternatives", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", +} +`; + +exports[`Serverless upgrade and rollback checks detect param changes to review for: xpack.ml.anomaly_detection_jobs_health 1`] = ` +Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "excludeJobs": Object { + "flags": Object { + "default": null, + "error": [Function], + "presence": "optional", + }, + "matches": Array [ + Object { + "schema": Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "groupIds": Object { + "flags": Object { + "default": Array [], + "error": [Function], + "presence": "optional", + }, + "items": Array [ + Object { + "flags": Object { + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + ], + "type": "array", + }, + "jobIds": Object { + "flags": Object { + "default": Array [], + "error": [Function], + "presence": "optional", + }, + "items": Array [ + Object { + "flags": Object { + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + ], + "type": "array", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "object", + }, + }, + Object { + "schema": Object { + "allow": Array [ + null, + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + ], + "type": "alternatives", + }, + "includeJobs": Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "groupIds": Object { + "flags": Object { + "default": Array [], + "error": [Function], + "presence": "optional", + }, + "items": Array [ + Object { + "flags": Object { + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + ], + "type": "array", + }, + "jobIds": Object { + "flags": Object { + "default": Array [], + "error": [Function], + "presence": "optional", + }, + "items": Array [ + Object { + "flags": Object { + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + ], + "type": "array", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "object", + }, + "testsConfig": Object { + "flags": Object { + "default": null, + "error": [Function], + "presence": "optional", + }, + "matches": Array [ + Object { + "schema": Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "behindRealtime": Object { + "flags": Object { + "default": null, + "error": [Function], + "presence": "optional", + }, + "matches": Array [ + Object { + "schema": Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "enabled": Object { + "flags": Object { + "default": true, + "error": [Function], + "presence": "optional", + }, + "type": "boolean", + }, + "timeInterval": Object { + "flags": Object { + "default": null, + "error": [Function], + "presence": "optional", + }, + "matches": Array [ + Object { + "schema": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + }, + Object { + "schema": Object { + "allow": Array [ + null, + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + ], + "type": "alternatives", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", + }, + }, + Object { + "schema": Object { + "allow": Array [ + null, + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + ], + "type": "alternatives", + }, + "datafeed": Object { + "flags": Object { + "default": null, + "error": [Function], + "presence": "optional", + }, + "matches": Array [ + Object { + "schema": Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "enabled": Object { + "flags": Object { + "default": true, + "error": [Function], + "presence": "optional", + }, + "type": "boolean", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", + }, + }, + Object { + "schema": Object { + "allow": Array [ + null, + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + ], + "type": "alternatives", + }, + "delayedData": Object { + "flags": Object { + "default": null, + "error": [Function], + "presence": "optional", + }, + "matches": Array [ + Object { + "schema": Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "docsCount": Object { + "flags": Object { + "default": null, + "error": [Function], + "presence": "optional", + }, + "matches": Array [ + Object { + "schema": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "limit": 1, + }, + "name": "min", + }, + ], + "type": "number", + }, + }, + Object { + "schema": Object { + "allow": Array [ + null, + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + ], + "type": "alternatives", + }, + "enabled": Object { + "flags": Object { + "default": true, + "error": [Function], + "presence": "optional", + }, + "type": "boolean", + }, + "timeInterval": Object { + "flags": Object { + "default": null, + "error": [Function], + "presence": "optional", + }, + "matches": Array [ + Object { + "schema": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + }, + Object { + "schema": Object { + "allow": Array [ + null, + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + ], + "type": "alternatives", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", + }, + }, + Object { + "schema": Object { + "allow": Array [ + null, + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + ], + "type": "alternatives", + }, + "errorMessages": Object { + "flags": Object { + "default": null, + "error": [Function], + "presence": "optional", + }, + "matches": Array [ + Object { + "schema": Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "enabled": Object { + "flags": Object { + "default": true, + "error": [Function], + "presence": "optional", + }, + "type": "boolean", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", + }, + }, + Object { + "schema": Object { + "allow": Array [ + null, + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + ], + "type": "alternatives", + }, + "mml": Object { + "flags": Object { + "default": null, + "error": [Function], + "presence": "optional", + }, + "matches": Array [ + Object { + "schema": Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "enabled": Object { + "flags": Object { + "default": true, + "error": [Function], + "presence": "optional", + }, + "type": "boolean", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", + }, + }, + Object { + "schema": Object { + "allow": Array [ + null, + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + ], + "type": "alternatives", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", + }, + }, + Object { + "schema": Object { + "allow": Array [ + null, + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + ], + "type": "alternatives", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", +} +`; diff --git a/x-pack/plugins/alerting/server/integration_tests/lib/setup_test_servers.ts b/x-pack/plugins/alerting/server/integration_tests/lib/setup_test_servers.ts index 7ded9629eb31e..4b722d5460213 100644 --- a/x-pack/plugins/alerting/server/integration_tests/lib/setup_test_servers.ts +++ b/x-pack/plugins/alerting/server/integration_tests/lib/setup_test_servers.ts @@ -5,7 +5,6 @@ * 2.0. */ -import deepmerge from 'deepmerge'; import { createTestServers, createRootWithCorePlugins } from '@kbn/core-test-helpers-kbn-server'; export async function setupTestServers(settings = {}) { @@ -20,25 +19,7 @@ export async function setupTestServers(settings = {}) { const esServer = await startES(); - const root = createRootWithCorePlugins( - deepmerge( - { - logging: { - root: { - level: 'warn', - }, - loggers: [ - { - name: 'plugins.taskManager', - level: 'all', - }, - ], - }, - }, - settings - ), - { oss: false } - ); + const root = createRootWithCorePlugins(settings, { oss: false }); await root.preboot(); const coreSetup = await root.setup(); diff --git a/x-pack/plugins/alerting/server/integration_tests/serverless_upgrade_and_rollback_checks.test.ts b/x-pack/plugins/alerting/server/integration_tests/serverless_upgrade_and_rollback_checks.test.ts new file mode 100644 index 0000000000000..07abf88d32221 --- /dev/null +++ b/x-pack/plugins/alerting/server/integration_tests/serverless_upgrade_and_rollback_checks.test.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 { + type TestElasticsearchUtils, + type TestKibanaUtils, +} from '@kbn/core-test-helpers-kbn-server'; +import { uniq } from 'lodash'; +import { zodToJsonSchema } from 'zod-to-json-schema'; +import { setupTestServers } from './lib'; +import type { RuleTypeRegistry } from '../rule_type_registry'; + +jest.mock('../rule_type_registry', () => { + const actual = jest.requireActual('../rule_type_registry'); + return { + ...actual, + RuleTypeRegistry: jest.fn().mockImplementation((opts) => { + return new actual.RuleTypeRegistry(opts); + }), + }; +}); + +/** + * These rule types are manually updated. + * + * TODO: We should spin up three serverless projects and pull the rule types + * directly from them to ensure the list below remains up to date. We will still + * need a copied list of rule types here because they are needed to write + * test scenarios below. + */ +const ruleTypesInEsProjects: string[] = [ + '.index-threshold', + '.geo-containment', + '.es-query', + 'transform_health', +]; +const ruleTypesInObltProjects: string[] = [ + '.index-threshold', + '.geo-containment', + '.es-query', + 'transform_health', + 'xpack.ml.anomaly_detection_alert', + 'xpack.ml.anomaly_detection_jobs_health', + 'slo.rules.burnRate', + 'observability.rules.custom_threshold', + 'metrics.alert.inventory.threshold', + 'apm.error_rate', + 'apm.transaction_error_rate', + 'apm.transaction_duration', + 'apm.anomaly', +]; +const ruleTypesInSecurityProjects: string[] = [ + '.index-threshold', + '.geo-containment', + '.es-query', + 'transform_health', + 'xpack.ml.anomaly_detection_alert', + 'xpack.ml.anomaly_detection_jobs_health', + 'siem.notifications', + 'siem.eqlRule', + 'siem.indicatorRule', + 'siem.mlRule', + 'siem.queryRule', + 'siem.savedQueryRule', + 'siem.thresholdRule', + 'siem.newTermsRule', +]; + +describe('Serverless upgrade and rollback checks', () => { + let esServer: TestElasticsearchUtils; + let kibanaServer: TestKibanaUtils; + let ruleTypeRegistry: RuleTypeRegistry; + const ruleTypesToCheck: string[] = uniq( + ruleTypesInEsProjects.concat(ruleTypesInObltProjects).concat(ruleTypesInSecurityProjects) + ); + + beforeAll(async () => { + const setupResult = await setupTestServers(); + esServer = setupResult.esServer; + kibanaServer = setupResult.kibanaServer; + + const mockedRuleTypeRegistry = jest.requireMock('../rule_type_registry'); + expect(mockedRuleTypeRegistry.RuleTypeRegistry).toHaveBeenCalledTimes(1); + ruleTypeRegistry = mockedRuleTypeRegistry.RuleTypeRegistry.mock.results[0].value; + }); + + afterAll(async () => { + if (kibanaServer) { + await kibanaServer.stop(); + } + if (esServer) { + await esServer.stop(); + } + }); + + for (const ruleTypeId of ruleTypesToCheck) { + test(`detect param changes to review for: ${ruleTypeId}`, async () => { + const ruleType = ruleTypeRegistry.get(ruleTypeId); + if (!ruleType?.schemas?.params) { + throw new Error('schema.params is required for rule type:' + ruleTypeId); + } + const schemaType = ruleType.schemas.params.type; + if (schemaType === 'config-schema') { + // @ts-ignore-next-line getSchema() exists.. + expect(ruleType.schemas.params.schema.getSchema().describe()).toMatchSnapshot(); + } else if (schemaType === 'zod') { + expect(zodToJsonSchema(ruleType.schemas.params.schema)).toMatchSnapshot(); + } else { + throw new Error(`Support for ${schemaType} missing`); + } + }); + } +}); diff --git a/x-pack/plugins/alerting/server/lib/get_alerts_for_notification.test.ts b/x-pack/plugins/alerting/server/lib/get_alerts_for_notification.test.ts index c1465d5b7a238..4656f4377f130 100644 --- a/x-pack/plugins/alerting/server/lib/get_alerts_for_notification.test.ts +++ b/x-pack/plugins/alerting/server/lib/get_alerts_for_notification.test.ts @@ -21,6 +21,7 @@ describe('getAlertsForNotification', () => { DEFAULT_FLAPPING_SETTINGS, true, 'default', + 0, { '1': alert1, }, @@ -89,6 +90,7 @@ describe('getAlertsForNotification', () => { DEFAULT_FLAPPING_SETTINGS, true, 'default', + 0, {}, {}, { @@ -222,6 +224,7 @@ describe('getAlertsForNotification', () => { DISABLE_FLAPPING_SETTINGS, true, 'default', + 0, {}, {}, { @@ -353,6 +356,7 @@ describe('getAlertsForNotification', () => { DEFAULT_FLAPPING_SETTINGS, false, 'default', + 0, {}, {}, { @@ -455,10 +459,11 @@ describe('getAlertsForNotification', () => { }); const alert2 = new Alert('2', { meta: { uuid: 'uuid-2' } }); - const { newAlerts, activeAlerts } = getAlertsForNotification( + const { newAlerts, activeAlerts, currentActiveAlerts } = getAlertsForNotification( DEFAULT_FLAPPING_SETTINGS, true, 'default', + 0, { '1': alert1, }, @@ -507,6 +512,30 @@ describe('getAlertsForNotification', () => { }, } `); + expect(currentActiveAlerts).toMatchInlineSnapshot(` + Object { + "1": Object { + "meta": Object { + "activeCount": 2, + "flappingHistory": Array [], + "maintenanceWindowIds": Array [], + "pendingRecoveredCount": 0, + "uuid": "uuid-1", + }, + "state": Object {}, + }, + "2": Object { + "meta": Object { + "activeCount": 1, + "flappingHistory": Array [], + "maintenanceWindowIds": Array [], + "pendingRecoveredCount": 0, + "uuid": "uuid-2", + }, + "state": Object {}, + }, + } + `); }); test('should reset activeCount for all recovered alerts', () => { @@ -517,6 +546,7 @@ describe('getAlertsForNotification', () => { DEFAULT_FLAPPING_SETTINGS, true, 'default', + 0, {}, {}, { @@ -574,4 +604,78 @@ describe('getAlertsForNotification', () => { } `); }); + + test('should remove the alert from newAlerts and should not return the alert in currentActiveAlerts if the activeCount is less than the rule alertDelay', () => { + const alert1 = new Alert('1', { + meta: { activeCount: 1, uuid: 'uuid-1' }, + }); + const alert2 = new Alert('2', { meta: { uuid: 'uuid-2' } }); + + const { newAlerts, activeAlerts, currentActiveAlerts } = getAlertsForNotification( + DEFAULT_FLAPPING_SETTINGS, + true, + 'default', + 5, + { + '1': alert1, + }, + { + '1': alert1, + '2': alert2, + }, + {}, + {} + ); + expect(newAlerts).toMatchInlineSnapshot(`Object {}`); + expect(activeAlerts).toMatchInlineSnapshot(` + Object { + "1": Object { + "meta": Object { + "activeCount": 2, + "flappingHistory": Array [], + "maintenanceWindowIds": Array [], + "pendingRecoveredCount": 0, + "uuid": "uuid-1", + }, + "state": Object {}, + }, + "2": Object { + "meta": Object { + "activeCount": 1, + "flappingHistory": Array [], + "maintenanceWindowIds": Array [], + "pendingRecoveredCount": 0, + "uuid": "uuid-2", + }, + "state": Object {}, + }, + } + `); + expect(currentActiveAlerts).toMatchInlineSnapshot(`Object {}`); + }); + + test('should update active alert to look like a new alert if the activeCount is equal to the rule alertDelay', () => { + const alert2 = new Alert('2', { meta: { uuid: 'uuid-2' } }); + + const { newAlerts, activeAlerts, currentActiveAlerts } = getAlertsForNotification( + DEFAULT_FLAPPING_SETTINGS, + true, + 'default', + 1, + {}, + { + '2': alert2, + }, + {}, + {} + ); + expect(newAlerts['2'].getState().duration).toBe('0'); + expect(newAlerts['2'].getState().start).toBeTruthy(); + + expect(activeAlerts['2'].getState().duration).toBe('0'); + expect(activeAlerts['2'].getState().start).toBeTruthy(); + + expect(currentActiveAlerts['2'].getState().duration).toBe('0'); + expect(currentActiveAlerts['2'].getState().start).toBeTruthy(); + }); }); diff --git a/x-pack/plugins/alerting/server/lib/get_alerts_for_notification.ts b/x-pack/plugins/alerting/server/lib/get_alerts_for_notification.ts index 63e95402549a4..593d92b35383b 100644 --- a/x-pack/plugins/alerting/server/lib/get_alerts_for_notification.ts +++ b/x-pack/plugins/alerting/server/lib/get_alerts_for_notification.ts @@ -19,10 +19,12 @@ export function getAlertsForNotification< flappingSettings: RulesSettingsFlappingProperties, notifyOnActionGroupChange: boolean, actionGroupId: string, + alertDelay: number, newAlerts: Record> = {}, activeAlerts: Record> = {}, recoveredAlerts: Record> = {}, - currentRecoveredAlerts: Record> = {} + currentRecoveredAlerts: Record> = {}, + startedAt?: string | null ) { const currentActiveAlerts: Record> = {}; @@ -30,7 +32,22 @@ export function getAlertsForNotification< const alert = activeAlerts[id]; alert.incrementActiveCount(); alert.resetPendingRecoveredCount(); - currentActiveAlerts[id] = alert; + // do not trigger an action notification if the number of consecutive + // active alerts is less than the rule alertDelay threshold + if (alert.getActiveCount() < alertDelay) { + // remove from new alerts + delete newAlerts[id]; + } else { + currentActiveAlerts[id] = alert; + // if the active count is equal to the alertDelay it is considered a new alert + if (alert.getActiveCount() === alertDelay) { + const currentTime = startedAt ?? new Date().toISOString(); + const state = alert.getState(); + // keep the state and update the start time and duration + alert.replaceState({ ...state, start: currentTime, duration: '0' }); + newAlerts[id] = alert; + } + } } for (const id of keys(currentRecoveredAlerts)) { diff --git a/x-pack/plugins/alerting/server/lib/wrap_scoped_cluster_client.test.ts b/x-pack/plugins/alerting/server/lib/wrap_scoped_cluster_client.test.ts index e1febe893d4d6..1880db1e69a4e 100644 --- a/x-pack/plugins/alerting/server/lib/wrap_scoped_cluster_client.test.ts +++ b/x-pack/plugins/alerting/server/lib/wrap_scoped_cluster_client.test.ts @@ -19,7 +19,7 @@ const eqlQuery = { }; const esqlQueryRequest = { method: 'POST', - path: '/_esql', + path: '/_query', body: { query: 'from .kibana_task_manager', }, @@ -291,7 +291,7 @@ describe('wrapScopedClusterClient', () => { }).client(); await expect( - wrappedSearchClient.asInternalUser.transport.request({ method: 'POST', path: '/_esql' }) + wrappedSearchClient.asInternalUser.transport.request({ method: 'POST', path: '/_query' }) ).rejects.toThrowErrorMatchingInlineSnapshot(`"something went wrong!"`); }); @@ -322,7 +322,7 @@ describe('wrapScopedClusterClient', () => { expect(stats.totalSearchDurationMs).toBeGreaterThan(-1); expect(logger.debug).toHaveBeenCalledWith( - `executing ES|QL query for rule .test-rule-type:abcdefg in space my-space - {\"method\":\"POST\",\"path\":\"/_esql\",\"body\":{\"query\":\"from .kibana_task_manager\"}} - with options {}` + `executing ES|QL query for rule .test-rule-type:abcdefg in space my-space - {\"method\":\"POST\",\"path\":\"/_query\",\"body\":{\"query\":\"from .kibana_task_manager\"}} - with options {}` ); }); @@ -342,7 +342,10 @@ describe('wrapScopedClusterClient', () => { }).client(); await expect( - abortableSearchClient.asInternalUser.transport.request({ method: 'POST', path: '/_esql' }) + abortableSearchClient.asInternalUser.transport.request({ + method: 'POST', + path: '/_query', + }) ).rejects.toThrowErrorMatchingInlineSnapshot( `"ES|QL search has been aborted due to cancelled execution"` ); diff --git a/x-pack/plugins/alerting/server/lib/wrap_scoped_cluster_client.ts b/x-pack/plugins/alerting/server/lib/wrap_scoped_cluster_client.ts index 9ddd22a292b4a..55f9d7f4a7c07 100644 --- a/x-pack/plugins/alerting/server/lib/wrap_scoped_cluster_client.ts +++ b/x-pack/plugins/alerting/server/lib/wrap_scoped_cluster_client.ts @@ -124,7 +124,7 @@ function getWrappedTransportRequestFn(opts: WrapEsClientOpts) { options?: TransportRequestOptions ): Promise> { // Wrap ES|QL requests with an abort signal - if (params.method === 'POST' && params.path === '/_esql') { + if (params.method === 'POST' && params.path === '/_query') { try { const requestOptions = options ?? {}; const start = Date.now(); diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/create/transforms/transform_create_body/v1.ts b/x-pack/plugins/alerting/server/routes/rule/apis/create/transforms/transform_create_body/v1.ts index fcf92b386aaa2..5dea295c40ed7 100644 --- a/x-pack/plugins/alerting/server/routes/rule/apis/create/transforms/transform_create_body/v1.ts +++ b/x-pack/plugins/alerting/server/routes/rule/apis/create/transforms/transform_create_body/v1.ts @@ -58,6 +58,6 @@ export const transformCreateBody = ( schedule: createBody.schedule, actions: transformCreateBodyActions(createBody.actions), ...(createBody.notify_when ? { notifyWhen: createBody.notify_when } : {}), - ...(createBody.notification_delay ? { notificationDelay: createBody.notification_delay } : {}), + ...(createBody.alert_delay ? { alertDelay: createBody.alert_delay } : {}), }; }; diff --git a/x-pack/plugins/alerting/server/routes/rule/transforms/transform_rule_to_rule_response/v1.ts b/x-pack/plugins/alerting/server/routes/rule/transforms/transform_rule_to_rule_response/v1.ts index 4f55401160ce9..f4fdedfc6f436 100644 --- a/x-pack/plugins/alerting/server/routes/rule/transforms/transform_rule_to_rule_response/v1.ts +++ b/x-pack/plugins/alerting/server/routes/rule/transforms/transform_rule_to_rule_response/v1.ts @@ -119,5 +119,5 @@ export const transformRuleToRuleResponse = ( ...(rule.viewInAppRelativeUrl !== undefined ? { view_in_app_relative_url: rule.viewInAppRelativeUrl } : {}), - ...(rule.notificationDelay !== undefined ? { notification_delay: rule.notificationDelay } : {}), + ...(rule.alertDelay !== undefined ? { alert_delay: rule.alertDelay } : {}), }); diff --git a/x-pack/plugins/alerting/server/saved_objects/schemas/raw_rule/v1.ts b/x-pack/plugins/alerting/server/saved_objects/schemas/raw_rule/v1.ts index 495c2493f2e43..e0641e9b275ea 100644 --- a/x-pack/plugins/alerting/server/saved_objects/schemas/raw_rule/v1.ts +++ b/x-pack/plugins/alerting/server/saved_objects/schemas/raw_rule/v1.ts @@ -213,7 +213,7 @@ const rawRuleActionSchema = schema.object({ useAlertDataForTemplate: schema.maybe(schema.boolean()), }); -export const notificationDelaySchema = schema.object({ +export const alertDelaySchema = schema.object({ active: schema.number(), }); @@ -274,5 +274,5 @@ export const rawRuleSchema = schema.object({ ), params: schema.recordOf(schema.string(), schema.maybe(schema.any())), typeVersion: schema.maybe(schema.number()), - notificationDelay: schema.maybe(notificationDelaySchema), + alertDelay: schema.maybe(alertDelaySchema), }); diff --git a/x-pack/plugins/alerting/server/task_runner/execution_handler.test.ts b/x-pack/plugins/alerting/server/task_runner/execution_handler.test.ts index 8f5147ea4de30..a742afb152b19 100644 --- a/x-pack/plugins/alerting/server/task_runner/execution_handler.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/execution_handler.test.ts @@ -2052,165 +2052,6 @@ describe('Execution Handler', () => { `); }); - test('does not schedule actions for alerts with activeCount less than the notificationDelay.active threshold', async () => { - const executionHandler = new ExecutionHandler( - generateExecutionParams({ - ...defaultExecutionParams, - rule: { - ...defaultExecutionParams.rule, - notificationDelay: { - active: 3, - }, - }, - }) - ); - - await executionHandler.run({ - ...generateAlert({ id: 1 }), - ...generateAlert({ id: 2, activeCount: 2 }), - }); - - expect(actionsClient.bulkEnqueueExecution).not.toHaveBeenCalled(); - expect(defaultExecutionParams.logger.debug).toHaveBeenCalledTimes(2); - - expect(defaultExecutionParams.logger.debug).toHaveBeenCalledWith( - 'no scheduling of action "1" for rule "1": the alert activeCount: 0 is less than the rule notificationDelay.active: 3 threshold.' - ); - expect(defaultExecutionParams.logger.debug).toHaveBeenCalledWith( - 'no scheduling of action "1" for rule "1": the alert activeCount: 2 is less than the rule notificationDelay.active: 3 threshold.' - ); - }); - - test('schedules actions for alerts with activeCount greater than or equal the notificationDelay.active threshold', async () => { - const executionHandler = new ExecutionHandler( - generateExecutionParams({ - ...defaultExecutionParams, - rule: { - ...defaultExecutionParams.rule, - notificationDelay: { - active: 3, - }, - }, - }) - ); - - await executionHandler.run({ - ...generateAlert({ id: 1, activeCount: 3 }), - ...generateAlert({ id: 2, activeCount: 4 }), - }); - - expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(1); - expect(actionsClient.bulkEnqueueExecution.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - Array [ - Object { - "actionTypeId": "test", - "apiKey": "MTIzOmFiYw==", - "consumer": "rule-consumer", - "executionId": "5f6aa57d-3e22-484e-bae8-cbed868f4d28", - "id": "1", - "params": Object { - "alertVal": "My 1 name-of-alert test1 tag-A,tag-B 1 goes here", - "contextVal": "My goes here", - "foo": true, - "stateVal": "My goes here", - }, - "relatedSavedObjects": Array [ - Object { - "id": "1", - "namespace": "test1", - "type": "alert", - "typeId": "test", - }, - ], - "source": Object { - "source": Object { - "id": "1", - "type": "alert", - }, - "type": "SAVED_OBJECT", - }, - "spaceId": "test1", - }, - Object { - "actionTypeId": "test", - "apiKey": "MTIzOmFiYw==", - "consumer": "rule-consumer", - "executionId": "5f6aa57d-3e22-484e-bae8-cbed868f4d28", - "id": "1", - "params": Object { - "alertVal": "My 1 name-of-alert test1 tag-A,tag-B 2 goes here", - "contextVal": "My goes here", - "foo": true, - "stateVal": "My goes here", - }, - "relatedSavedObjects": Array [ - Object { - "id": "1", - "namespace": "test1", - "type": "alert", - "typeId": "test", - }, - ], - "source": Object { - "source": Object { - "id": "1", - "type": "alert", - }, - "type": "SAVED_OBJECT", - }, - "spaceId": "test1", - }, - ], - ] - `); - }); - - test('schedules actions if notificationDelay.active threshold is not defined', async () => { - const executionHandler = new ExecutionHandler(generateExecutionParams()); - - await executionHandler.run({ - ...generateAlert({ id: 1, activeCount: 1 }), - }); - - expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(1); - expect(actionsClient.bulkEnqueueExecution.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - Array [ - Object { - "actionTypeId": "test", - "apiKey": "MTIzOmFiYw==", - "consumer": "rule-consumer", - "executionId": "5f6aa57d-3e22-484e-bae8-cbed868f4d28", - "id": "1", - "params": Object { - "alertVal": "My 1 name-of-alert test1 tag-A,tag-B 1 goes here", - "contextVal": "My goes here", - "foo": true, - "stateVal": "My goes here", - }, - "relatedSavedObjects": Array [ - Object { - "id": "1", - "namespace": "test1", - "type": "alert", - "typeId": "test", - }, - ], - "source": Object { - "source": Object { - "id": "1", - "type": "alert", - }, - "type": "SAVED_OBJECT", - }, - "spaceId": "test1", - }, - ], - ] - `); - }); - describe('rule url', () => { const ruleWithUrl = { ...rule, diff --git a/x-pack/plugins/alerting/server/task_runner/execution_handler.ts b/x-pack/plugins/alerting/server/task_runner/execution_handler.ts index e118b4d327ce1..ec690bb8ba0f5 100644 --- a/x-pack/plugins/alerting/server/task_runner/execution_handler.ts +++ b/x-pack/plugins/alerting/server/task_runner/execution_handler.ts @@ -628,22 +628,6 @@ export class ExecutionHandler< continue; } - if ( - this.rule.notificationDelay && - alert.getActiveCount() < this.rule.notificationDelay.active - ) { - this.logger.debug( - `no scheduling of action "${action.id}" for rule "${ - this.taskInstance.params.alertId - }": the alert activeCount: ${alert.getActiveCount()} is less than the rule notificationDelay.active: ${ - this.rule.notificationDelay.active - } threshold.` - ); - continue; - } else { - alert.resetActiveCount(); - } - const actionGroup = this.getActionGroup(alert); if (!this.ruleTypeActionGroups!.has(actionGroup)) { diff --git a/x-pack/plugins/alerting/server/task_runner/fixtures.ts b/x-pack/plugins/alerting/server/task_runner/fixtures.ts index 3d647966414f5..b2a984ea5768f 100644 --- a/x-pack/plugins/alerting/server/task_runner/fixtures.ts +++ b/x-pack/plugins/alerting/server/task_runner/fixtures.ts @@ -454,7 +454,7 @@ export const generateAlertInstance = ( flapping: false, maintenanceWindowIds: maintenanceWindowIds || [], pendingRecoveredCount: 0, - activeCount: 0, + activeCount: 1, }, state: { bar: false, diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts index d14d44010252d..e4afa351d4f14 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts @@ -2954,7 +2954,7 @@ describe('Task Runner', () => { maintenanceWindowIds: [], flapping: false, pendingRecoveredCount: 0, - activeCount: 0, + activeCount: 1, }, state: { duration: '0', @@ -3125,7 +3125,7 @@ describe('Task Runner', () => { maintenanceWindowIds: [], flapping: false, pendingRecoveredCount: 0, - activeCount: 0, + activeCount: 1, }, state: { duration: '0', @@ -3143,7 +3143,7 @@ describe('Task Runner', () => { maintenanceWindowIds: [], flapping: false, pendingRecoveredCount: 0, - activeCount: 0, + activeCount: 1, }, state: { duration: '0', diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.ts index 208e46b88a1f6..93f655965e92a 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.ts @@ -257,6 +257,7 @@ export class TaskRunner< revision: rule.revision, spaceId, tags: rule.tags, + alertDelay: rule.alertDelay?.active ?? 0, }; } @@ -311,6 +312,7 @@ export class TaskRunner< muteAll, revision, snoozeSchedule, + alertDelay, } = rule; const { params: { alertId: ruleId, spaceId }, @@ -525,6 +527,7 @@ export class TaskRunner< notifyWhen, muteAll, snoozeSchedule, + alertDelay, }, logger: this.logger, flappingSettings, @@ -582,6 +585,7 @@ export class TaskRunner< notifyWhen === RuleNotifyWhen.CHANGE || some(actions, (action) => action.frequency?.notifyWhen === RuleNotifyWhen.CHANGE), maintenanceWindowIds: maintenanceWindowsWithoutScopedQueryIds, + alertDelay: alertDelay?.active ?? 0, }); }); diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner_alerts_client.test.ts b/x-pack/plugins/alerting/server/task_runner/task_runner_alerts_client.test.ts index ebf81f5bf050e..bd47acbbdb8c1 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner_alerts_client.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner_alerts_client.test.ts @@ -302,6 +302,7 @@ describe('Task Runner', () => { ruleType: ruleTypeWithAlerts, namespace: 'default', rule: { + alertDelay: 0, consumer: 'bar', executionId: '5f6aa57d-3e22-484e-bae8-cbed868f4d28', id: '1', @@ -800,6 +801,7 @@ describe('Task Runner', () => { expect(alertsClientToUse.processAlerts).toHaveBeenCalledWith({ notifyOnActionGroupChange: false, + alertDelay: 0, flappingSettings: { enabled: true, lookBackWindow: 20, diff --git a/x-pack/plugins/alerting/server/types.ts b/x-pack/plugins/alerting/server/types.ts index b03c27d1dbe48..eeb13576ce39d 100644 --- a/x-pack/plugins/alerting/server/types.ts +++ b/x-pack/plugins/alerting/server/types.ts @@ -11,6 +11,7 @@ import type { SavedObjectReference, IUiSettingsClient, } from '@kbn/core/server'; +import z from 'zod'; import { DataViewsContract } from '@kbn/data-views-plugin/common'; import { ISearchStartSearchSource } from '@kbn/data-plugin/common'; import { LicenseType } from '@kbn/licensing-plugin/server'; @@ -20,6 +21,7 @@ import { SavedObjectsClientContract, Logger, } from '@kbn/core/server'; +import type { ObjectType } from '@kbn/config-schema'; import type { PublicMethodsOf } from '@kbn/utility-types'; import { SharePluginStart } from '@kbn/share-plugin/server'; import type { DefaultAlert, FieldMap } from '@kbn/alerts-as-data-utils'; @@ -60,7 +62,7 @@ import { AlertsFilter, AlertsFilterTimeframe, RuleAlertData, - NotificationDelay, + AlertDelay, } from '../common'; import { PublicAlertFactory } from './alert/create_alert_factory'; import { RulesSettingsFlappingProperties } from '../common/rules_settings'; @@ -287,6 +289,17 @@ export interface RuleType< validate: { params: RuleTypeParamsValidator; }; + schemas?: { + params?: + | { + type: 'zod'; + schema: z.ZodObject | z.ZodIntersection; + } + | { + type: 'config-schema'; + schema: ObjectType; + }; + }; actionGroups: Array>; defaultActionGroupId: ActionGroup['id']; recoveryActionGroup?: ActionGroup; @@ -410,7 +423,6 @@ export type PublicRuleResultService = PublicLastRunSetters; export interface RawRuleLastRun extends SavedObjectAttributes, RuleLastRun {} export interface RawRuleMonitoring extends SavedObjectAttributes, RuleMonitoring {} -export interface RawNotificationDelay extends SavedObjectAttributes, NotificationDelay {} export interface RawRuleAlertsFilter extends AlertsFilter { query?: { @@ -487,7 +499,7 @@ export interface RawRule extends SavedObjectAttributes { nextRun?: string | null; revision: number; running?: boolean | null; - notificationDelay?: RawNotificationDelay; + alertDelay?: AlertDelay; } export type { DataStreamAdapter } from './alerts_service/lib/data_stream_adapter'; diff --git a/x-pack/plugins/apm/public/components/app/dependency_detail_operations/dependency_detail_operations_list/index.tsx b/x-pack/plugins/apm/public/components/app/dependency_detail_operations/dependency_detail_operations_list/index.tsx index 35516c1da58fd..7b7a4089172bf 100644 --- a/x-pack/plugins/apm/public/components/app/dependency_detail_operations/dependency_detail_operations_list/index.tsx +++ b/x-pack/plugins/apm/public/components/app/dependency_detail_operations/dependency_detail_operations_list/index.tsx @@ -63,7 +63,8 @@ export function DependencyDetailOperationsList() { const { core } = useApmPluginContext(); - const breakpoints = useBreakpoints(); + const { isLarge } = useBreakpoints(); + const shouldShowSparkPlots = !isLarge; const { start, end } = useTimeRange({ rangeFrom, @@ -147,7 +148,7 @@ export function DependencyDetailOperationsList() { render: (_, { spanName }) => , }, ...getSpanMetricColumns({ - breakpoints, + shouldShowSparkPlots, comparisonFetchStatus: comparisonStatsFetch.status, }), ]; diff --git a/x-pack/plugins/apm/public/components/app/mobile/service_overview/index.tsx b/x-pack/plugins/apm/public/components/app/mobile/service_overview/index.tsx index 55104eefaa071..dd0e2cd9968cc 100644 --- a/x-pack/plugins/apm/public/components/app/mobile/service_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/mobile/service_overview/index.tsx @@ -193,7 +193,6 @@ export function MobileServiceOverview() { kuery={kueryWithMobileFilters} environment={environment} fixedHeight={true} - isSingleColumn={isSingleColumn} start={start} end={end} showPerPageOptions={false} diff --git a/x-pack/plugins/apm/public/components/app/service_overview/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/index.tsx index 4b2ab0ad6c3a8..743843c825210 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/index.tsx @@ -135,11 +135,11 @@ export function ServiceOverview() { kuery={kuery} environment={environment} fixedHeight={true} - isSingleColumn={isSingleColumn} start={start} end={end} showPerPageOptions={false} numberOfTransactionsPerPage={5} + showSparkPlots={!isSingleColumn} /> @@ -209,6 +209,7 @@ export function ServiceOverview() { )} } + showSparkPlots={!isSingleColumn} /> diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx index f95905dd3dc57..e8c9dffa375b0 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx @@ -24,12 +24,14 @@ interface ServiceOverviewDependenciesTableProps { fixedHeight?: boolean; link?: ReactNode; showPerPageOptions?: boolean; + showSparkPlots?: boolean; } export function ServiceOverviewDependenciesTable({ fixedHeight, link, showPerPageOptions = true, + showSparkPlots, }: ServiceOverviewDependenciesTableProps) { const { query: { @@ -171,6 +173,7 @@ export function ServiceOverviewDependenciesTable({ link={link} showPerPageOptions={showPerPageOptions} initialPageSize={5} + showSparkPlots={showSparkPlots} /> ); } diff --git a/x-pack/plugins/apm/public/components/shared/dependencies_table/get_span_metric_columns.tsx b/x-pack/plugins/apm/public/components/shared/dependencies_table/get_span_metric_columns.tsx index d477e7269cace..07d1b75ffb238 100644 --- a/x-pack/plugins/apm/public/components/shared/dependencies_table/get_span_metric_columns.tsx +++ b/x-pack/plugins/apm/public/components/shared/dependencies_table/get_span_metric_columns.tsx @@ -13,7 +13,6 @@ import { RIGHT_ALIGNMENT, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { Breakpoints } from '../../../hooks/use_breakpoints'; import { ChartType, getTimeSeriesColor, @@ -53,14 +52,12 @@ export interface SpanMetricGroup { } export function getSpanMetricColumns({ - breakpoints, comparisonFetchStatus, + shouldShowSparkPlots, }: { - breakpoints: Breakpoints; comparisonFetchStatus: FETCH_STATUS; + shouldShowSparkPlots: boolean; }): Array> { - const { isLarge } = breakpoints; - const shouldShowSparkPlots = !isLarge; const isLoading = isPending(comparisonFetchStatus); return [ diff --git a/x-pack/plugins/apm/public/components/shared/dependencies_table/index.tsx b/x-pack/plugins/apm/public/components/shared/dependencies_table/index.tsx index c4d1a0bdaffe1..dec4f35327e57 100644 --- a/x-pack/plugins/apm/public/components/shared/dependencies_table/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/dependencies_table/index.tsx @@ -38,6 +38,7 @@ interface Props { status: FETCH_STATUS; compact?: boolean; showPerPageOptions?: boolean; + showSparkPlots?: boolean; } type FormattedSpanMetricGroup = SpanMetricGroup & { @@ -56,10 +57,11 @@ export function DependenciesTable(props: Props) { compact = true, showPerPageOptions = true, initialPageSize, + showSparkPlots, } = props; - // SparkPlots should be hidden if we're in two-column view and size XL (1200px) - const breakpoints = useBreakpoints(); + const { isLarge } = useBreakpoints(); + const shouldShowSparkPlots = showSparkPlots ?? !isLarge; const items: FormattedSpanMetricGroup[] = dependencies.map((dependency) => ({ name: dependency.name, @@ -95,7 +97,7 @@ export function DependenciesTable(props: Props) { width: '30%', }, ...getSpanMetricColumns({ - breakpoints, + shouldShowSparkPlots, comparisonFetchStatus: status, }), ]; diff --git a/x-pack/plugins/apm/public/components/shared/transactions_table/index.tsx b/x-pack/plugins/apm/public/components/shared/transactions_table/index.tsx index e4553dfee073a..0d9621efd18c8 100644 --- a/x-pack/plugins/apm/public/components/shared/transactions_table/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/transactions_table/index.tsx @@ -48,7 +48,6 @@ const INITIAL_STATE: ApiResponse & { requestId: string } = { interface Props { hideTitle?: boolean; hideViewTransactionsLink?: boolean; - isSingleColumn?: boolean; numberOfTransactionsPerPage?: number; showPerPageOptions?: boolean; showMaxTransactionGroupsExceededWarning?: boolean; @@ -58,13 +57,13 @@ interface Props { start: string; end: string; saveTableOptionsToUrl?: boolean; + showSparkPlots?: boolean; } export function TransactionsTable({ fixedHeight = false, hideViewTransactionsLink = false, hideTitle = false, - isSingleColumn = true, numberOfTransactionsPerPage = 10, showPerPageOptions = true, showMaxTransactionGroupsExceededWarning = false, @@ -73,6 +72,7 @@ export function TransactionsTable({ start, end, saveTableOptionsToUrl = false, + showSparkPlots, }: Props) { const { link } = useApmRouter(); @@ -94,9 +94,8 @@ export function TransactionsTable({ latencyAggregationTypeFromQuery ); - // SparkPlots should be hidden if we're in two-column view and size XL (1200px) - const { isXl } = useBreakpoints(); - const shouldShowSparkPlots = isSingleColumn || !isXl; + const { isLarge } = useBreakpoints(); + const shouldShowSparkPlots = showSparkPlots ?? !isLarge; const { transactionType, serviceName } = useApmServiceContext(); const [searchQuery, setSearchQueryDebounced] = useStateDebounced(''); diff --git a/x-pack/plugins/apm/server/routes/alerts/rule_types/anomaly/register_anomaly_rule_type.ts b/x-pack/plugins/apm/server/routes/alerts/rule_types/anomaly/register_anomaly_rule_type.ts index ba7d803f91f90..3165a72fbe134 100644 --- a/x-pack/plugins/apm/server/routes/alerts/rule_types/anomaly/register_anomaly_rule_type.ts +++ b/x-pack/plugins/apm/server/routes/alerts/rule_types/anomaly/register_anomaly_rule_type.ts @@ -82,6 +82,12 @@ export function registerAnomalyRuleType({ actionGroups: ruleTypeConfig.actionGroups, defaultActionGroupId: ruleTypeConfig.defaultActionGroupId, validate: { params: anomalyParamsSchema }, + schemas: { + params: { + type: 'config-schema', + schema: anomalyParamsSchema, + }, + }, actionVariables: { context: [ apmActionVariables.alertDetailsUrl, diff --git a/x-pack/plugins/apm/server/routes/alerts/rule_types/error_count/register_error_count_rule_type.ts b/x-pack/plugins/apm/server/routes/alerts/rule_types/error_count/register_error_count_rule_type.ts index ba053dbcb8577..5c3c2d3fe4fb2 100644 --- a/x-pack/plugins/apm/server/routes/alerts/rule_types/error_count/register_error_count_rule_type.ts +++ b/x-pack/plugins/apm/server/routes/alerts/rule_types/error_count/register_error_count_rule_type.ts @@ -92,6 +92,12 @@ export function registerErrorCountRuleType({ actionGroups: ruleTypeConfig.actionGroups, defaultActionGroupId: ruleTypeConfig.defaultActionGroupId, validate: { params: errorCountParamsSchema }, + schemas: { + params: { + type: 'config-schema', + schema: errorCountParamsSchema, + }, + }, actionVariables: { context: errorCountActionVariables, }, diff --git a/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_duration/register_transaction_duration_rule_type.ts b/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_duration/register_transaction_duration_rule_type.ts index ef0cb2beb21c9..dc33d5380f705 100644 --- a/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_duration/register_transaction_duration_rule_type.ts +++ b/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_duration/register_transaction_duration_rule_type.ts @@ -104,6 +104,12 @@ export function registerTransactionDurationRuleType({ actionGroups: ruleTypeConfig.actionGroups, defaultActionGroupId: ruleTypeConfig.defaultActionGroupId, validate: { params: transactionDurationParamsSchema }, + schemas: { + params: { + type: 'config-schema', + schema: transactionDurationParamsSchema, + }, + }, actionVariables: { context: transactionDurationActionVariables, }, diff --git a/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_error_rate/register_transaction_error_rate_rule_type.ts b/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_error_rate/register_transaction_error_rate_rule_type.ts index 30c8c6fb96b96..2eabee53c8d22 100644 --- a/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_error_rate/register_transaction_error_rate_rule_type.ts +++ b/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_error_rate/register_transaction_error_rate_rule_type.ts @@ -101,6 +101,12 @@ export function registerTransactionErrorRateRuleType({ actionGroups: ruleTypeConfig.actionGroups, defaultActionGroupId: ruleTypeConfig.defaultActionGroupId, validate: { params: transactionErrorRateParamsSchema }, + schemas: { + params: { + type: 'config-schema', + schema: transactionErrorRateParamsSchema, + }, + }, actionVariables: { context: transactionErrorRateActionVariables, }, diff --git a/x-pack/plugins/cases/common/types/api/case/v1.ts b/x-pack/plugins/cases/common/types/api/case/v1.ts index 9efac679085e4..acb8049e01737 100644 --- a/x-pack/plugins/cases/common/types/api/case/v1.ts +++ b/x-pack/plugins/cases/common/types/api/case/v1.ts @@ -22,7 +22,6 @@ import { MAX_CATEGORY_FILTER_LENGTH, MAX_ASSIGNEES_PER_CASE, MAX_CUSTOM_FIELDS_PER_CASE, - MAX_CUSTOM_FIELD_TEXT_VALUE_LENGTH, } from '../../../constants'; import { limitedStringSchema, @@ -42,17 +41,12 @@ import { import { CaseConnectorRt } from '../../domain/connector/v1'; import { CaseUserProfileRt, UserRt } from '../../domain/user/v1'; import { CasesStatusResponseRt } from '../stats/v1'; - -const CaseCustomFieldTextWithValidationValueRt = limitedStringSchema({ - fieldName: 'value', - min: 1, - max: MAX_CUSTOM_FIELD_TEXT_VALUE_LENGTH, -}); +import { CaseCustomFieldTextWithValidationValueRt } from '../custom_field/v1'; const CaseCustomFieldTextWithValidationRt = rt.strict({ key: rt.string, type: CustomFieldTextTypeRt, - value: rt.union([CaseCustomFieldTextWithValidationValueRt, rt.null]), + value: rt.union([CaseCustomFieldTextWithValidationValueRt('value'), rt.null]), }); const CustomFieldRt = rt.union([CaseCustomFieldTextWithValidationRt, CaseCustomFieldToggleRt]); diff --git a/x-pack/plugins/cases/common/types/api/configure/v1.test.ts b/x-pack/plugins/cases/common/types/api/configure/v1.test.ts index b69e7701db69c..3369cb8473c0c 100644 --- a/x-pack/plugins/cases/common/types/api/configure/v1.test.ts +++ b/x-pack/plugins/cases/common/types/api/configure/v1.test.ts @@ -11,6 +11,7 @@ import { MAX_CUSTOM_FIELDS_PER_CASE, MAX_CUSTOM_FIELD_KEY_LENGTH, MAX_CUSTOM_FIELD_LABEL_LENGTH, + MAX_CUSTOM_FIELD_TEXT_VALUE_LENGTH, } from '../../../constants'; import { ConnectorTypes } from '../../domain/connector/v1'; import { CustomFieldTypes } from '../../domain/custom_field/v1'; @@ -311,6 +312,18 @@ describe('configure', () => { }); }); + it('has expected attributes in request with defaultValue', () => { + const query = TextCustomFieldConfigurationRt.decode({ + ...defaultRequest, + defaultValue: 'foobar', + }); + + expect(query).toStrictEqual({ + _tag: 'Right', + right: { ...defaultRequest, defaultValue: 'foobar' }, + }); + }); + it('removes foo:bar attributes from request', () => { const query = TextCustomFieldConfigurationRt.decode({ ...defaultRequest, foo: 'bar' }); @@ -319,6 +332,41 @@ describe('configure', () => { right: { ...defaultRequest }, }); }); + + it('defaultValue fails if the type is not string', () => { + expect( + PathReporter.report( + TextCustomFieldConfigurationRt.decode({ + ...defaultRequest, + defaultValue: false, + }) + )[0] + ).toContain('Invalid value false supplied'); + }); + + it(`throws an error if the default value is longer than ${MAX_CUSTOM_FIELD_TEXT_VALUE_LENGTH}`, () => { + expect( + PathReporter.report( + TextCustomFieldConfigurationRt.decode({ + ...defaultRequest, + defaultValue: '#'.repeat(MAX_CUSTOM_FIELD_TEXT_VALUE_LENGTH + 1), + }) + )[0] + ).toContain( + `The length of the defaultValue is too long. The maximum length is ${MAX_CUSTOM_FIELD_TEXT_VALUE_LENGTH}.` + ); + }); + + it('throws an error if the default value is an empty string', () => { + expect( + PathReporter.report( + TextCustomFieldConfigurationRt.decode({ + ...defaultRequest, + defaultValue: '', + }) + )[0] + ).toContain('The defaultValue field cannot be an empty string.'); + }); }); describe('ToggleCustomFieldConfigurationRt', () => { @@ -346,5 +394,17 @@ describe('configure', () => { right: { ...defaultRequest }, }); }); + + it('defaultValue fails if the type is not boolean', () => { + expect( + PathReporter.report( + ToggleCustomFieldConfigurationRt.decode({ + ...defaultRequest, + required: true, + defaultValue: 'foobar', + }) + )[0] + ).toContain('Invalid value "foobar" supplied'); + }); }); }); diff --git a/x-pack/plugins/cases/common/types/api/configure/v1.ts b/x-pack/plugins/cases/common/types/api/configure/v1.ts index 8f98b760c9186..8e986677ae8a9 100644 --- a/x-pack/plugins/cases/common/types/api/configure/v1.ts +++ b/x-pack/plugins/cases/common/types/api/configure/v1.ts @@ -16,6 +16,7 @@ import { CustomFieldTextTypeRt, CustomFieldToggleTypeRt } from '../../domain'; import type { Configurations, Configuration } from '../../domain/configure/v1'; import { ConfigurationBasicWithoutOwnerRt, ClosureTypeRt } from '../../domain/configure/v1'; import { CaseConnectorRt } from '../../domain/connector/v1'; +import { CaseCustomFieldTextWithValidationValueRt } from '../custom_field/v1'; export const CustomFieldConfigurationWithoutTypeRt = rt.strict({ /** @@ -39,11 +40,21 @@ export const CustomFieldConfigurationWithoutTypeRt = rt.strict({ export const TextCustomFieldConfigurationRt = rt.intersection([ rt.strict({ type: CustomFieldTextTypeRt }), CustomFieldConfigurationWithoutTypeRt, + rt.exact( + rt.partial({ + defaultValue: rt.union([CaseCustomFieldTextWithValidationValueRt('defaultValue'), rt.null]), + }) + ), ]); export const ToggleCustomFieldConfigurationRt = rt.intersection([ rt.strict({ type: CustomFieldToggleTypeRt }), CustomFieldConfigurationWithoutTypeRt, + rt.exact( + rt.partial({ + defaultValue: rt.union([rt.boolean, rt.null]), + }) + ), ]); export const CustomFieldsConfigurationRt = limitedArraySchema({ diff --git a/x-pack/plugins/cases/common/types/api/custom_field/latest.ts b/x-pack/plugins/cases/common/types/api/custom_field/latest.ts new file mode 100644 index 0000000000000..25300c97a6d2e --- /dev/null +++ b/x-pack/plugins/cases/common/types/api/custom_field/latest.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './v1'; diff --git a/x-pack/plugins/cases/common/types/api/custom_field/v1.test.ts b/x-pack/plugins/cases/common/types/api/custom_field/v1.test.ts new file mode 100644 index 0000000000000..e2f54761d6670 --- /dev/null +++ b/x-pack/plugins/cases/common/types/api/custom_field/v1.test.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 { PathReporter } from 'io-ts/lib/PathReporter'; +import { MAX_CUSTOM_FIELD_TEXT_VALUE_LENGTH } from '../../../constants'; +import { CaseCustomFieldTextWithValidationValueRt } from './v1'; + +describe('Custom Fields', () => { + describe('CaseCustomFieldTextWithValidationValueRt', () => { + const customFieldValueType = CaseCustomFieldTextWithValidationValueRt('value'); + + it('decodes strings correctly', () => { + const query = customFieldValueType.decode('foobar'); + + expect(query).toStrictEqual({ + _tag: 'Right', + right: 'foobar', + }); + }); + + it('the value cannot be empty', () => { + expect(PathReporter.report(customFieldValueType.decode(''))[0]).toContain( + 'The value field cannot be an empty string.' + ); + }); + + it(`limits the length to ${MAX_CUSTOM_FIELD_TEXT_VALUE_LENGTH}`, () => { + expect( + PathReporter.report( + customFieldValueType.decode('#'.repeat(MAX_CUSTOM_FIELD_TEXT_VALUE_LENGTH + 1)) + )[0] + ).toContain( + `The length of the value is too long. The maximum length is ${MAX_CUSTOM_FIELD_TEXT_VALUE_LENGTH}.` + ); + }); + }); +}); diff --git a/x-pack/plugins/cases/common/types/api/custom_field/v1.ts b/x-pack/plugins/cases/common/types/api/custom_field/v1.ts new file mode 100644 index 0000000000000..4ee70642c86b1 --- /dev/null +++ b/x-pack/plugins/cases/common/types/api/custom_field/v1.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { MAX_CUSTOM_FIELD_TEXT_VALUE_LENGTH } from '../../../constants'; +import { limitedStringSchema } from '../../../schema'; + +export const CaseCustomFieldTextWithValidationValueRt = (fieldName: string) => + limitedStringSchema({ + fieldName, + min: 1, + max: MAX_CUSTOM_FIELD_TEXT_VALUE_LENGTH, + }); diff --git a/x-pack/plugins/cases/common/types/api/index.ts b/x-pack/plugins/cases/common/types/api/index.ts index f36e8e476dbe9..9e8459dd6894b 100644 --- a/x-pack/plugins/cases/common/types/api/index.ts +++ b/x-pack/plugins/cases/common/types/api/index.ts @@ -16,6 +16,7 @@ export * from './user/latest'; export * from './connector/latest'; export * from './attachment/latest'; export * from './metrics/latest'; +export * from './custom_field/latest'; // V1 export * as configureApiV1 from './configure/v1'; @@ -28,3 +29,4 @@ export * as userApiV1 from './user/v1'; export * as connectorApiV1 from './connector/v1'; export * as attachmentApiV1 from './attachment/v1'; export * as metricsApiV1 from './metrics/v1'; +export * as customFieldsApiV1 from './custom_field/v1'; diff --git a/x-pack/plugins/cases/common/types/domain/configure/v1.test.ts b/x-pack/plugins/cases/common/types/domain/configure/v1.test.ts index 9af0d2b474b76..400d69700fe12 100644 --- a/x-pack/plugins/cases/common/types/domain/configure/v1.test.ts +++ b/x-pack/plugins/cases/common/types/domain/configure/v1.test.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { PathReporter } from 'io-ts/lib/PathReporter'; import { ConnectorTypes } from '../connector/v1'; import { CustomFieldTypes } from '../custom_field/v1'; import { @@ -192,10 +193,10 @@ describe('configure', () => { key: 'my_text_custom_field', label: 'Text Custom Field', type: CustomFieldTypes.TEXT, - required: true, + required: false, }; - it('has expected attributes in request', () => { + it('has expected attributes in request with required: false', () => { const query = TextCustomFieldConfigurationRt.decode(defaultRequest); expect(query).toStrictEqual({ @@ -204,6 +205,35 @@ describe('configure', () => { }); }); + it('has expected attributes in request with defaultValue and required: true', () => { + const query = TextCustomFieldConfigurationRt.decode({ + ...defaultRequest, + required: true, + defaultValue: 'foobar', + }); + + expect(query).toStrictEqual({ + _tag: 'Right', + right: { + ...defaultRequest, + required: true, + defaultValue: 'foobar', + }, + }); + }); + + it('defaultValue fails if the type is not string', () => { + expect( + PathReporter.report( + TextCustomFieldConfigurationRt.decode({ + ...defaultRequest, + required: true, + defaultValue: false, + }) + )[0] + ).toContain('Invalid value false supplied'); + }); + it('removes foo:bar attributes from request', () => { const query = TextCustomFieldConfigurationRt.decode({ ...defaultRequest, foo: 'bar' }); @@ -222,7 +252,7 @@ describe('configure', () => { required: false, }; - it('has expected attributes in request', () => { + it('has expected attributes in request with required: false', () => { const query = ToggleCustomFieldConfigurationRt.decode(defaultRequest); expect(query).toStrictEqual({ @@ -231,6 +261,35 @@ describe('configure', () => { }); }); + it('has expected attributes in request with defaultValue and required: true', () => { + const query = ToggleCustomFieldConfigurationRt.decode({ + ...defaultRequest, + required: true, + defaultValue: false, + }); + + expect(query).toStrictEqual({ + _tag: 'Right', + right: { + ...defaultRequest, + required: true, + defaultValue: false, + }, + }); + }); + + it('defaultValue fails if the type is not boolean', () => { + expect( + PathReporter.report( + ToggleCustomFieldConfigurationRt.decode({ + ...defaultRequest, + required: true, + defaultValue: 'foobar', + }) + )[0] + ).toContain('Invalid value "foobar" supplied'); + }); + it('removes foo:bar attributes from request', () => { const query = ToggleCustomFieldConfigurationRt.decode({ ...defaultRequest, foo: 'bar' }); diff --git a/x-pack/plugins/cases/common/types/domain/configure/v1.ts b/x-pack/plugins/cases/common/types/domain/configure/v1.ts index 56a144c248c2a..65882ad40753e 100644 --- a/x-pack/plugins/cases/common/types/domain/configure/v1.ts +++ b/x-pack/plugins/cases/common/types/domain/configure/v1.ts @@ -33,11 +33,21 @@ export const CustomFieldConfigurationWithoutTypeRt = rt.strict({ export const TextCustomFieldConfigurationRt = rt.intersection([ rt.strict({ type: CustomFieldTextTypeRt }), CustomFieldConfigurationWithoutTypeRt, + rt.exact( + rt.partial({ + defaultValue: rt.union([rt.string, rt.null]), + }) + ), ]); export const ToggleCustomFieldConfigurationRt = rt.intersection([ rt.strict({ type: CustomFieldToggleTypeRt }), CustomFieldConfigurationWithoutTypeRt, + rt.exact( + rt.partial({ + defaultValue: rt.union([rt.boolean, rt.null]), + }) + ), ]); export const CustomFieldConfigurationRt = rt.union([ diff --git a/x-pack/plugins/cases/public/common/test_utils.tsx b/x-pack/plugins/cases/public/common/test_utils.tsx index e7c02cfb78602..65ec29abf90c1 100644 --- a/x-pack/plugins/cases/public/common/test_utils.tsx +++ b/x-pack/plugins/cases/public/common/test_utils.tsx @@ -62,7 +62,9 @@ export const FormTestComponent: React.FC = ({ return (
{children} - form.submit()}>{'Submit'} + form.submit()} data-test-subj="form-test-component-submit-button"> + {'Submit'} +
); }; diff --git a/x-pack/plugins/cases/public/components/case_view/components/custom_fields.test.tsx b/x-pack/plugins/cases/public/components/case_view/components/custom_fields.test.tsx index ce532a41d64e9..9b1037959120b 100644 --- a/x-pack/plugins/cases/public/components/case_view/components/custom_fields.test.tsx +++ b/x-pack/plugins/cases/public/components/case_view/components/custom_fields.test.tsx @@ -38,8 +38,10 @@ describe('Case View Page files tab', () => { /> ); - expect(screen.getByTestId('case-custom-field-wrapper-test_key_1')).toBeInTheDocument(); - expect(screen.getByTestId('case-custom-field-wrapper-test_key_2')).toBeInTheDocument(); + expect(await screen.findByTestId('case-custom-field-wrapper-test_key_1')).toBeInTheDocument(); + expect(await screen.findByTestId('case-custom-field-wrapper-test_key_2')).toBeInTheDocument(); + expect(await screen.findByTestId('case-custom-field-wrapper-test_key_3')).toBeInTheDocument(); + expect(await screen.findByTestId('case-custom-field-wrapper-test_key_4')).toBeInTheDocument(); }); it('should render the custom fields types when the custom fields are empty', async () => { @@ -52,11 +54,11 @@ describe('Case View Page files tab', () => { /> ); - expect(screen.getByTestId('case-custom-field-wrapper-test_key_1')).toBeInTheDocument(); - expect(screen.getByTestId('case-custom-field-wrapper-test_key_2')).toBeInTheDocument(); + expect(await screen.findByTestId('case-custom-field-wrapper-test_key_1')).toBeInTheDocument(); + expect(await screen.findByTestId('case-custom-field-wrapper-test_key_2')).toBeInTheDocument(); }); - it('should not show the custom fields if the configuration is empty', async () => { + it('should not show the custom fields if the configuration is empty', () => { appMockRender.render( { /> ); - const customFields = screen.getAllByTestId('case-custom-field-wrapper', { exact: false }); + const customFields = await screen.findAllByTestId('case-custom-field-wrapper', { + exact: false, + }); - expect(customFields.length).toBe(2); + expect(customFields.length).toBe(4); - expect(within(customFields[0]).getByRole('heading')).toHaveTextContent('My test label 1'); - expect(within(customFields[1]).getByRole('heading')).toHaveTextContent('My test label 2'); + expect(await within(customFields[0]).findByRole('heading')).toHaveTextContent( + 'My test label 1' + ); + expect(await within(customFields[1]).findByRole('heading')).toHaveTextContent( + 'My test label 2' + ); + expect(await within(customFields[2]).findByRole('heading')).toHaveTextContent( + 'My test label 3' + ); + expect(await within(customFields[3]).findByRole('heading')).toHaveTextContent( + 'My test label 4' + ); }); it('pass the permissions to custom fields correctly', async () => { @@ -117,16 +131,18 @@ describe('Case View Page files tab', () => { /> ); - userEvent.click(screen.getByRole('switch')); + userEvent.click((await screen.findAllByRole('switch'))[0]); await waitFor(() => { expect(onSubmit).toBeCalledWith([ { type: CustomFieldTypes.TEXT, key: 'test_key_1', - value: null, + value: customFieldsConfigurationMock[0].defaultValue, }, { type: CustomFieldTypes.TOGGLE, key: 'test_key_2', value: true }, + customFieldsMock[2], + customFieldsMock[3], ]); }); }); @@ -141,16 +157,84 @@ describe('Case View Page files tab', () => { /> ); - userEvent.click(screen.getByRole('switch')); + userEvent.click((await screen.findAllByRole('switch'))[0]); await waitFor(() => { expect(onSubmit).toBeCalledWith([ { type: CustomFieldTypes.TEXT, key: 'test_key_1', - value: null, + value: customFieldsConfigurationMock[0].defaultValue, }, { type: CustomFieldTypes.TOGGLE, key: 'test_key_2', value: false }, + customFieldsMock[2], + customFieldsMock[3], + ]); + }); + }); + + it('adds missing defaultValues to required text custom fields without value', async () => { + appMockRender.render( + + ); + + // Clicking the toggle triggers the form submit + userEvent.click((await screen.findAllByRole('switch'))[0]); + + await waitFor(() => { + expect(onSubmit).toBeCalledWith([ + { + type: CustomFieldTypes.TEXT, + key: 'test_key_1', + value: customFieldsConfigurationMock[0].defaultValue, + }, + { + type: CustomFieldTypes.TOGGLE, + key: 'test_key_2', + value: false, + }, + ]); + }); + }); + + it('does not overwrite existing text values with a configured defaultValue', async () => { + appMockRender.render( + + ); + + userEvent.click((await screen.findAllByRole('switch'))[0]); + + await waitFor(() => { + expect(onSubmit).toBeCalledWith([ + { + type: CustomFieldTypes.TEXT, + key: 'test_key_1', + value: 'existing value', + }, + { + type: CustomFieldTypes.TOGGLE, + key: 'test_key_2', + value: false, + }, ]); }); }); @@ -172,7 +256,7 @@ describe('Case View Page files tab', () => { /> ); - userEvent.click(screen.getByRole('switch')); + userEvent.click(await screen.findByRole('switch')); await waitFor(() => { expect(onSubmit).toBeCalledWith([ @@ -191,12 +275,14 @@ describe('Case View Page files tab', () => { /> ); - userEvent.click(screen.getByRole('switch')); + userEvent.click((await screen.findAllByRole('switch'))[0]); await waitFor(() => { expect(onSubmit).toBeCalledWith([ customFieldsMock[0], { type: CustomFieldTypes.TOGGLE, key: 'test_key_2', value: false }, + customFieldsMock[2], + customFieldsMock[3], ]); }); }); diff --git a/x-pack/plugins/cases/public/components/case_view/components/custom_fields.tsx b/x-pack/plugins/cases/public/components/case_view/components/custom_fields.tsx index 5d178c2709f62..b1bb01672c0dc 100644 --- a/x-pack/plugins/cases/public/components/case_view/components/custom_fields.tsx +++ b/x-pack/plugins/cases/public/components/case_view/components/custom_fields.tsx @@ -97,11 +97,25 @@ const createMissingAndRemoveExtraCustomFields = ( (customField) => customField.key === confCustomField.key ); + const shouldUseDefaultValue = Boolean( + confCustomField.required && confCustomField?.defaultValue + ); + if (foundCustomField) { - return foundCustomField; + return { + ...foundCustomField, + value: + foundCustomField.value == null && shouldUseDefaultValue + ? confCustomField.defaultValue + : foundCustomField.value, + } as CaseUICustomField; } - return { key: confCustomField.key, type: confCustomField.type, value: null }; + return { + key: confCustomField.key, + type: confCustomField.type, + value: shouldUseDefaultValue ? confCustomField.defaultValue : null, + } as CaseUICustomField; }); return createdCustomFields; diff --git a/x-pack/plugins/cases/public/components/configure_cases/index.test.tsx b/x-pack/plugins/cases/public/components/configure_cases/index.test.tsx index 6420b33eefbf5..e2db3717c009d 100644 --- a/x-pack/plugins/cases/public/components/configure_cases/index.test.tsx +++ b/x-pack/plugins/cases/public/components/configure_cases/index.test.tsx @@ -701,7 +701,11 @@ describe('ConfigureCases', () => { fields: null, }, closureType: 'close-by-user', - customFields: [{ ...customFieldsConfigurationMock[1] }], + customFields: [ + { ...customFieldsConfigurationMock[1] }, + { ...customFieldsConfigurationMock[2] }, + { ...customFieldsConfigurationMock[3] }, + ], id: '', version: '', }); @@ -728,9 +732,7 @@ describe('ConfigureCases', () => { expect(await screen.findByTestId('custom-field-flyout')).toBeInTheDocument(); userEvent.paste(screen.getByTestId('custom-field-label-input'), '!!'); - - userEvent.click(screen.getByTestId('text-custom-field-options')); - + userEvent.click(screen.getByTestId('text-custom-field-required')); userEvent.click(screen.getByTestId('custom-field-flyout-save')); await waitFor(() => { @@ -744,11 +746,14 @@ describe('ConfigureCases', () => { closureType: 'close-by-user', customFields: [ { - ...customFieldsConfigurationMock[0], + key: customFieldsConfigurationMock[0].key, + type: customFieldsConfigurationMock[0].type, label: `${customFieldsConfigurationMock[0].label}!!`, required: !customFieldsConfigurationMock[0].required, }, { ...customFieldsConfigurationMock[1] }, + { ...customFieldsConfigurationMock[2] }, + { ...customFieldsConfigurationMock[3] }, ], id: '', version: '', diff --git a/x-pack/plugins/cases/public/components/create/custom_fields.test.tsx b/x-pack/plugins/cases/public/components/create/custom_fields.test.tsx index 06f6c17922221..864ba68ff690a 100644 --- a/x-pack/plugins/cases/public/components/create/custom_fields.test.tsx +++ b/x-pack/plugins/cases/public/components/create/custom_fields.test.tsx @@ -25,19 +25,19 @@ describe('CustomFields', () => { appMockRender = createAppMockRenderer(); }); - it('renders correctly', () => { + it('renders correctly', async () => { appMockRender.render( ); - expect(screen.getByText(i18n.ADDITIONAL_FIELDS)).toBeInTheDocument(); - expect(screen.getByTestId('create-case-custom-fields')).toBeInTheDocument(); + expect(await screen.findByText(i18n.ADDITIONAL_FIELDS)).toBeInTheDocument(); + expect(await screen.findByTestId('create-case-custom-fields')).toBeInTheDocument(); for (const item of customFieldsConfigurationMock) { expect( - screen.getByTestId(`${item.key}-${item.type}-create-custom-field`) + await screen.findByTestId(`${item.key}-${item.type}-create-custom-field`) ).toBeInTheDocument(); } }); @@ -66,10 +66,12 @@ describe('CustomFields', () => { const customFields = customFieldsWrapper.querySelectorAll('.euiFormRow'); - expect(customFields).toHaveLength(2); + expect(customFields).toHaveLength(4); expect(customFields[0]).toHaveTextContent('My test label 1'); expect(customFields[1]).toHaveTextContent('My test label 2'); + expect(customFields[2]).toHaveTextContent('My test label 3'); + expect(customFields[3]).toHaveTextContent('My test label 4'); }); it('should update the custom fields', async () => { @@ -81,24 +83,26 @@ describe('CustomFields', () => { ); - const textField = customFieldsConfigurationMock[0]; - const toggleField = customFieldsConfigurationMock[1]; + const textField = customFieldsConfigurationMock[2]; + const toggleField = customFieldsConfigurationMock[3]; userEvent.type( - screen.getByTestId(`${textField.key}-${textField.type}-create-custom-field`), + await screen.findByTestId(`${textField.key}-${textField.type}-create-custom-field`), 'hello' ); userEvent.click( - screen.getByTestId(`${toggleField.key}-${toggleField.type}-create-custom-field`) + await screen.findByTestId(`${toggleField.key}-${toggleField.type}-create-custom-field`) ); - userEvent.click(screen.getByText('Submit')); + userEvent.click(await screen.findByText('Submit')); await waitFor(() => { // data, isValid expect(onSubmit).toHaveBeenCalledWith( { customFields: { + [customFieldsConfigurationMock[0].key]: customFieldsConfigurationMock[0].defaultValue, + [customFieldsConfigurationMock[1].key]: customFieldsConfigurationMock[1].defaultValue, [textField.key]: 'hello', [toggleField.key]: true, }, diff --git a/x-pack/plugins/cases/public/components/custom_fields/custom_fields_list/index.test.tsx b/x-pack/plugins/cases/public/components/custom_fields/custom_fields_list/index.test.tsx index b7c87f3356d38..002d3e65b4e61 100644 --- a/x-pack/plugins/cases/public/components/custom_fields/custom_fields_list/index.test.tsx +++ b/x-pack/plugins/cases/public/components/custom_fields/custom_fields_list/index.test.tsx @@ -44,14 +44,14 @@ describe('CustomFieldsList', () => { `custom-field-${customFieldsConfigurationMock[0].key}-${customFieldsConfigurationMock[0].type}` ) ).toBeInTheDocument(); - expect(await screen.findByText('Text')).toBeInTheDocument(); - expect(await screen.findByText('Required')).toBeInTheDocument(); + expect((await screen.findAllByText('Text')).length).toBe(2); + expect((await screen.findAllByText('Required')).length).toBe(2); expect( await screen.findByTestId( `custom-field-${customFieldsConfigurationMock[1].key}-${customFieldsConfigurationMock[1].type}` ) ).toBeInTheDocument(); - expect(await screen.findByText('Toggle')).toBeInTheDocument(); + expect((await screen.findAllByText('Toggle')).length).toBe(2); }); it('shows single CustomFieldsList correctly', async () => { diff --git a/x-pack/plugins/cases/public/components/custom_fields/flyout.test.tsx b/x-pack/plugins/cases/public/components/custom_fields/flyout.test.tsx index 63dc30d69c2e0..3a25009450df7 100644 --- a/x-pack/plugins/cases/public/components/custom_fields/flyout.test.tsx +++ b/x-pack/plugins/cases/public/components/custom_fields/flyout.test.tsx @@ -6,22 +6,22 @@ */ import React from 'react'; -import { screen, waitFor } from '@testing-library/react'; +import { fireEvent, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import type { AppMockRenderer } from '../../common/mock'; import { createAppMockRenderer } from '../../common/mock'; import { CustomFieldFlyout } from './flyout'; import { customFieldsConfigurationMock } from '../../containers/mock'; -import { MAX_CUSTOM_FIELD_LABEL_LENGTH } from '../../../common/constants'; +import { + MAX_CUSTOM_FIELD_LABEL_LENGTH, + MAX_CUSTOM_FIELD_TEXT_VALUE_LENGTH, +} from '../../../common/constants'; +import { CustomFieldTypes } from '../../../common/types/domain'; + import * as i18n from './translations'; -// FLAKY: https://github.com/elastic/kibana/issues/174285 -// FLAKY: https://github.com/elastic/kibana/issues/174286 -// FLAKY: https://github.com/elastic/kibana/issues/174287 -// FLAKY: https://github.com/elastic/kibana/issues/174288 -// FLAKY: https://github.com/elastic/kibana/issues/174289 -describe.skip('CustomFieldFlyout ', () => { +describe('CustomFieldFlyout ', () => { let appMockRender: AppMockRenderer; const props = { @@ -40,103 +40,209 @@ describe.skip('CustomFieldFlyout ', () => { it('renders correctly', async () => { appMockRender.render(); - expect(screen.getByTestId('custom-field-flyout-header')).toBeInTheDocument(); - expect(screen.getByTestId('custom-field-flyout-cancel')).toBeInTheDocument(); - expect(screen.getByTestId('custom-field-flyout-save')).toBeInTheDocument(); + expect(await screen.findByTestId('custom-field-flyout-header')).toBeInTheDocument(); + expect(await screen.findByTestId('custom-field-flyout-cancel')).toBeInTheDocument(); + expect(await screen.findByTestId('custom-field-flyout-save')).toBeInTheDocument(); }); - it('calls onSaveField on save field', async () => { + it('shows error if field label is too long', async () => { appMockRender.render(); - userEvent.paste(screen.getByTestId('custom-field-label-input'), 'Summary'); + const message = 'z'.repeat(MAX_CUSTOM_FIELD_LABEL_LENGTH + 1); + + userEvent.type(await screen.findByTestId('custom-field-label-input'), message); - userEvent.click(screen.getByTestId('text-custom-field-options')); + expect( + await screen.findByText( + i18n.MAX_LENGTH_ERROR(i18n.FIELD_LABEL.toLocaleLowerCase(), MAX_CUSTOM_FIELD_LABEL_LENGTH) + ) + ).toBeInTheDocument(); + }); + + it('does not call onSaveField when error', async () => { + appMockRender.render(); - userEvent.click(screen.getByTestId('custom-field-flyout-save')); + userEvent.click(await screen.findByTestId('custom-field-flyout-save')); + + expect( + await screen.findByText(i18n.REQUIRED_FIELD(i18n.FIELD_LABEL.toLocaleLowerCase())) + ).toBeInTheDocument(); + + expect(props.onSaveField).not.toBeCalled(); + }); + + it('calls onCloseFlyout on cancel', async () => { + appMockRender.render(); + + userEvent.click(await screen.findByTestId('custom-field-flyout-cancel')); await waitFor(() => { - expect(props.onSaveField).toBeCalledWith({ - key: expect.anything(), - label: 'Summary', - required: true, - type: 'text', - }); + expect(props.onCloseFlyout).toBeCalled(); }); }); - it('shows error if field label is too long', async () => { + it('calls onCloseFlyout on close', async () => { appMockRender.render(); - const message = 'z'.repeat(MAX_CUSTOM_FIELD_LABEL_LENGTH + 1); - - userEvent.type(screen.getByTestId('custom-field-label-input'), message); + userEvent.click(await screen.findByTestId('euiFlyoutCloseButton')); await waitFor(() => { - expect( - screen.getByText(i18n.MAX_LENGTH_ERROR('field label', MAX_CUSTOM_FIELD_LABEL_LENGTH)) - ).toBeInTheDocument(); + expect(props.onCloseFlyout).toBeCalled(); }); }); - it('calls onSaveField with serialized data', async () => { - appMockRender.render(); + describe('Text custom field', () => { + it('calls onSaveField with correct params when a custom field is NOT required', async () => { + appMockRender.render(); - userEvent.paste(screen.getByTestId('custom-field-label-input'), 'Summary'); + userEvent.paste(await screen.findByTestId('custom-field-label-input'), 'Summary'); + userEvent.click(await screen.findByTestId('custom-field-flyout-save')); - userEvent.click(screen.getByTestId('custom-field-flyout-save')); + await waitFor(() => { + expect(props.onSaveField).toBeCalledWith({ + key: expect.anything(), + label: 'Summary', + required: false, + type: CustomFieldTypes.TEXT, + }); + }); + }); - await waitFor(() => { - expect(props.onSaveField).toBeCalledWith({ - key: expect.anything(), - label: 'Summary', - required: false, - type: 'text', + it('calls onSaveField with the correct params when a custom field is required', async () => { + appMockRender.render(); + + userEvent.paste(await screen.findByTestId('custom-field-label-input'), 'Summary'); + userEvent.click(await screen.findByTestId('text-custom-field-required')); + userEvent.paste( + await screen.findByTestId('text-custom-field-default-value'), + 'Default value' + ); + userEvent.click(await screen.findByTestId('custom-field-flyout-save')); + + await waitFor(() => { + expect(props.onSaveField).toBeCalledWith({ + key: expect.anything(), + label: 'Summary', + required: true, + type: CustomFieldTypes.TEXT, + defaultValue: 'Default value', + }); }); }); - }); - it('does not call onSaveField when error', async () => { - appMockRender.render(); + it('calls onSaveField with the correct params when a custom field is required and the defaultValue is missing', async () => { + appMockRender.render(); - userEvent.click(screen.getByTestId('custom-field-flyout-save')); + userEvent.paste(await screen.findByTestId('custom-field-label-input'), 'Summary'); + userEvent.click(await screen.findByTestId('text-custom-field-required')); + userEvent.click(await screen.findByTestId('custom-field-flyout-save')); - await waitFor(() => { - expect(screen.getByText(i18n.REQUIRED_FIELD(i18n.FIELD_LABEL))).toBeInTheDocument(); + await waitFor(() => { + expect(props.onSaveField).toBeCalledWith({ + key: expect.anything(), + label: 'Summary', + required: true, + type: CustomFieldTypes.TEXT, + }); + }); }); - expect(props.onSaveField).not.toBeCalled(); - }); + it('renders flyout with the correct data when an initial customField value exists', async () => { + appMockRender.render( + + ); + + expect(await screen.findByTestId('custom-field-label-input')).toHaveAttribute( + 'value', + customFieldsConfigurationMock[0].label + ); + expect(await screen.findByTestId('custom-field-type-selector')).toHaveAttribute('disabled'); + expect(await screen.findByTestId('text-custom-field-required')).toHaveAttribute('checked'); + expect(await screen.findByTestId('text-custom-field-default-value')).toHaveAttribute( + 'value', + customFieldsConfigurationMock[0].defaultValue + ); + }); - it('calls onCloseFlyout on cancel', async () => { - appMockRender.render(); + it('shows an error if default value is too long', async () => { + appMockRender.render(); - userEvent.click(screen.getByTestId('custom-field-flyout-cancel')); + userEvent.paste(await screen.findByTestId('custom-field-label-input'), 'Summary'); + userEvent.click(await screen.findByTestId('text-custom-field-required')); + userEvent.paste( + await screen.findByTestId('text-custom-field-default-value'), + 'z'.repeat(MAX_CUSTOM_FIELD_TEXT_VALUE_LENGTH + 1) + ); - await waitFor(() => { - expect(props.onCloseFlyout).toBeCalled(); + expect( + await screen.findByText( + i18n.MAX_LENGTH_ERROR( + i18n.DEFAULT_VALUE.toLowerCase(), + MAX_CUSTOM_FIELD_TEXT_VALUE_LENGTH + ) + ) + ).toBeInTheDocument(); }); }); - it('calls onCloseFlyout on close', async () => { - appMockRender.render(); + describe('Toggle custom field', () => { + it('calls onSaveField with correct params when a custom field is NOT required', async () => { + appMockRender.render(); - userEvent.click(screen.getByTestId('euiFlyoutCloseButton')); + fireEvent.change(await screen.findByTestId('custom-field-type-selector'), { + target: { value: CustomFieldTypes.TOGGLE }, + }); - await waitFor(() => { - expect(props.onCloseFlyout).toBeCalled(); + userEvent.paste(await screen.findByTestId('custom-field-label-input'), 'Summary'); + userEvent.click(await screen.findByTestId('custom-field-flyout-save')); + + await waitFor(() => { + expect(props.onSaveField).toBeCalledWith({ + key: expect.anything(), + label: 'Summary', + required: false, + type: CustomFieldTypes.TOGGLE, + }); + }); }); - }); - it('renders flyout with data when customField value exist', async () => { - appMockRender.render( - - ); - - expect(await screen.findByTestId('custom-field-label-input')).toHaveAttribute( - 'value', - customFieldsConfigurationMock[0].label - ); - expect(await screen.findByTestId('custom-field-type-selector')).toHaveAttribute('disabled'); - expect(await screen.findByTestId('text-custom-field-options')).toHaveAttribute('checked'); + it('calls onSaveField with the correct default value when a custom field is required', async () => { + appMockRender.render(); + + fireEvent.change(await screen.findByTestId('custom-field-type-selector'), { + target: { value: CustomFieldTypes.TOGGLE }, + }); + + userEvent.paste(await screen.findByTestId('custom-field-label-input'), 'Summary'); + userEvent.click(await screen.findByTestId('toggle-custom-field-required')); + userEvent.click(await screen.findByTestId('custom-field-flyout-save')); + + await waitFor(() => { + expect(props.onSaveField).toBeCalledWith({ + key: expect.anything(), + label: 'Summary', + required: true, + type: CustomFieldTypes.TOGGLE, + defaultValue: false, + }); + }); + }); + + it('renders flyout with the correct data when an initial customField value exists', async () => { + appMockRender.render( + + ); + + expect(await screen.findByTestId('custom-field-label-input')).toHaveAttribute( + 'value', + customFieldsConfigurationMock[1].label + ); + expect(await screen.findByTestId('custom-field-type-selector')).toHaveAttribute('disabled'); + expect(await screen.findByTestId('toggle-custom-field-required')).toHaveAttribute('checked'); + expect(await screen.findByTestId('toggle-custom-field-default-value')).toHaveAttribute( + 'aria-checked', + 'true' + ); + }); }); }); diff --git a/x-pack/plugins/cases/public/components/custom_fields/flyout.tsx b/x-pack/plugins/cases/public/components/custom_fields/flyout.tsx index bf2013898e0c3..0be2c4ea43bcb 100644 --- a/x-pack/plugins/cases/public/components/custom_fields/flyout.tsx +++ b/x-pack/plugins/cases/public/components/custom_fields/flyout.tsx @@ -81,7 +81,6 @@ const CustomFieldFlyoutComponent: React.FC = ({ {i18n.CANCEL} - { it('renders correctly', async () => { appMockRender.render(); - expect(screen.getByTestId('custom-field-label-input')).toBeInTheDocument(); - expect(screen.getByTestId('custom-field-type-selector')).toBeInTheDocument(); + expect(await screen.findByTestId('custom-field-label-input')).toBeInTheDocument(); + expect(await screen.findByTestId('custom-field-type-selector')).toBeInTheDocument(); }); it('renders text as default custom field type', async () => { appMockRender.render(); - expect(screen.getByTestId('custom-field-type-selector')).toBeInTheDocument(); - expect(screen.getByText('Text')).toBeInTheDocument(); + expect(await screen.findByTestId('custom-field-type-selector')).toBeInTheDocument(); + expect(await screen.findByText('Text')).toBeInTheDocument(); - expect(screen.getByText(i18n.FIELD_OPTION_REQUIRED)).toBeInTheDocument(); + expect(await screen.findByText(i18n.FIELD_OPTION_REQUIRED)).toBeInTheDocument(); }); it('renders custom field type options', async () => { appMockRender.render(); - expect(screen.getByText('Text')).toBeInTheDocument(); - expect(screen.getByText('Toggle')).toBeInTheDocument(); - expect(screen.getByTestId('custom-field-type-selector')).not.toHaveAttribute('disabled'); + expect(await screen.findByText('Text')).toBeInTheDocument(); + expect(await screen.findByText('Toggle')).toBeInTheDocument(); + expect(await screen.findByTestId('custom-field-type-selector')).not.toHaveAttribute('disabled'); }); it('renders toggle custom field type', async () => { appMockRender.render(); - fireEvent.change(screen.getByTestId('custom-field-type-selector'), { + fireEvent.change(await screen.findByTestId('custom-field-type-selector'), { target: { value: CustomFieldTypes.TOGGLE }, }); - expect(screen.getByTestId('toggle-custom-field-options')).toBeInTheDocument(); - expect(screen.getByText(i18n.FIELD_OPTION_REQUIRED)).toBeInTheDocument(); + expect(await screen.findByTestId('toggle-custom-field-required')).toBeInTheDocument(); + expect(await screen.findByText(i18n.FIELD_OPTION_REQUIRED)).toBeInTheDocument(); }); it('serializes the data correctly if required is selected', async () => { @@ -77,8 +78,9 @@ describe('CustomFieldsForm ', () => { expect(formState).not.toBeUndefined(); }); - userEvent.paste(screen.getByTestId('custom-field-label-input'), 'Summary'); - userEvent.click(screen.getByTestId('text-custom-field-options')); + userEvent.paste(await screen.findByTestId('custom-field-label-input'), 'Summary'); + userEvent.click(await screen.findByTestId('text-custom-field-required')); + userEvent.paste(await screen.findByTestId('text-custom-field-default-value'), 'Default value'); await act(async () => { const { data } = await formState!.submit(); @@ -88,6 +90,101 @@ describe('CustomFieldsForm ', () => { label: 'Summary', required: true, type: 'text', + defaultValue: 'Default value', + }); + }); + }); + + it('serializes the data correctly if required is selected and the text default value is not filled', async () => { + let formState: CustomFieldFormState; + + const onChangeState = (state: CustomFieldFormState) => (formState = state); + + appMockRender.render(); + + await waitFor(() => { + expect(formState).not.toBeUndefined(); + }); + + userEvent.paste(await screen.findByTestId('custom-field-label-input'), 'Summary'); + userEvent.click(await screen.findByTestId('text-custom-field-required')); + + await act(async () => { + const { data } = await formState!.submit(); + + expect(data).toEqual({ + key: expect.anything(), + label: 'Summary', + required: true, + type: 'text', + }); + }); + }); + + it('serializes the data correctly if required is selected and the text default value is an empty string', async () => { + let formState: CustomFieldFormState; + + const onChangeState = (state: CustomFieldFormState) => (formState = state); + + appMockRender.render(); + + await waitFor(() => { + expect(formState).not.toBeUndefined(); + }); + + userEvent.paste(await screen.findByTestId('custom-field-label-input'), 'Summary'); + userEvent.click(await screen.findByTestId('text-custom-field-required')); + userEvent.paste(await screen.findByTestId('text-custom-field-default-value'), ' '); + + await act(async () => { + const { data } = await formState!.submit(); + + expect(data).toEqual({ + key: expect.anything(), + label: 'Summary', + required: true, + type: 'text', + }); + }); + }); + + it('serializes the data correctly if the initial default value is null', async () => { + let formState: CustomFieldFormState; + + const onChangeState = (state: CustomFieldFormState) => (formState = state); + + const initialValue = { + required: true, + type: CustomFieldTypes.TEXT as const, + defaultValue: null, + }; + + appMockRender.render( + + ); + + await waitFor(() => { + expect(formState).not.toBeUndefined(); + }); + + userEvent.paste(await screen.findByTestId('custom-field-label-input'), ' New'); + + await act(async () => { + const { data } = await formState!.submit(); + + expect(data).toEqual({ + key: expect.anything(), + label: 'Summary New', + ...initialValue, }); }); }); @@ -103,7 +200,7 @@ describe('CustomFieldsForm ', () => { expect(formState).not.toBeUndefined(); }); - userEvent.paste(screen.getByTestId('custom-field-label-input'), 'Summary'); + userEvent.paste(await screen.findByTestId('custom-field-label-input'), 'Summary'); await act(async () => { const { data } = await formState!.submit(); @@ -117,7 +214,7 @@ describe('CustomFieldsForm ', () => { }); }); - it('deserializes the data correctly if required is selected', async () => { + it('deserializes the "type: text" custom field data correctly', async () => { let formState: CustomFieldFormState; const onChangeState = (state: CustomFieldFormState) => (formState = state); @@ -136,7 +233,11 @@ describe('CustomFieldsForm ', () => { 'value', customFieldsConfigurationMock[0].label ); - expect(await screen.findByTestId('text-custom-field-options')).toHaveAttribute('checked'); + expect(await screen.findByTestId('text-custom-field-required')).toHaveAttribute('checked'); + expect(await screen.findByTestId('text-custom-field-default-value')).toHaveAttribute( + 'value', + customFieldsConfigurationMock[0].defaultValue + ); await act(async () => { const { data } = await formState!.submit(); @@ -145,7 +246,7 @@ describe('CustomFieldsForm ', () => { }); }); - it('deserializes the data correctly if required not selected', async () => { + it('deserializes the "type: toggle" custom field data correctly', async () => { let formState: CustomFieldFormState; const onChangeState = (state: CustomFieldFormState) => (formState = state); @@ -164,7 +265,11 @@ describe('CustomFieldsForm ', () => { 'value', customFieldsConfigurationMock[1].label ); - expect(await screen.findByTestId('text-custom-field-options')).not.toHaveAttribute('checked'); + expect(await screen.findByTestId('toggle-custom-field-required')).toHaveAttribute('checked'); + expect(await screen.findByTestId('toggle-custom-field-default-value')).toHaveAttribute( + 'aria-checked', + 'true' + ); await act(async () => { const { data } = await formState!.submit(); diff --git a/x-pack/plugins/cases/public/components/custom_fields/form.tsx b/x-pack/plugins/cases/public/components/custom_fields/form.tsx index f4e8568281af1..230b947db854d 100644 --- a/x-pack/plugins/cases/public/components/custom_fields/form.tsx +++ b/x-pack/plugins/cases/public/components/custom_fields/form.tsx @@ -10,11 +10,11 @@ import { Form, useForm } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_l import React, { useEffect, useMemo } from 'react'; import { v4 as uuidv4 } from 'uuid'; -import type { CustomFieldsConfigurationFormProps } from './schema'; import { schema } from './schema'; import { FormFields } from './form_fields'; import type { CustomFieldConfiguration } from '../../../common/types/domain'; import { CustomFieldTypes } from '../../../common/types/domain'; +import { customFieldSerializer } from './utils'; export interface CustomFieldFormState { isValid: boolean | undefined; @@ -26,36 +26,6 @@ interface Props { initialValue: CustomFieldConfiguration | null; } -// Form -> API -const formSerializer = ({ - key, - label, - type, - options, -}: CustomFieldsConfigurationFormProps): CustomFieldConfiguration => { - return { - key, - label, - type, - required: options?.required ? options.required : false, - }; -}; - -// API -> Form -const formDeserializer = ({ - key, - label, - type, - required, -}: CustomFieldConfiguration): CustomFieldsConfigurationFormProps => { - return { - key, - options: { required: Boolean(required) }, - label, - type, - }; -}; - const FormComponent: React.FC = ({ onChange, initialValue }) => { const keyDefaultValue = useMemo(() => uuidv4(), []); @@ -68,8 +38,7 @@ const FormComponent: React.FC = ({ onChange, initialValue }) => { }, options: { stripEmptyFields: false }, schema, - serializer: formSerializer, - deserializer: formDeserializer, + serializer: customFieldSerializer, }); const { submit, isValid, isSubmitting } = form; diff --git a/x-pack/plugins/cases/public/components/custom_fields/form_fields.test.tsx b/x-pack/plugins/cases/public/components/custom_fields/form_fields.test.tsx index 5f7c4be9873f2..6c392a1ee7d7d 100644 --- a/x-pack/plugins/cases/public/components/custom_fields/form_fields.test.tsx +++ b/x-pack/plugins/cases/public/components/custom_fields/form_fields.test.tsx @@ -31,8 +31,8 @@ describe('FormFields ', () => { ); - expect(screen.getByTestId('custom-field-label-input')).toBeInTheDocument(); - expect(screen.getByTestId('custom-field-type-selector')).toBeInTheDocument(); + expect(await screen.findByTestId('custom-field-label-input')).toBeInTheDocument(); + expect(await screen.findByTestId('custom-field-type-selector')).toBeInTheDocument(); }); it('disables field type selector on edit mode', async () => { @@ -42,7 +42,7 @@ describe('FormFields ', () => { ); - expect(screen.getByTestId('custom-field-type-selector')).toHaveAttribute('disabled'); + expect(await screen.findByTestId('custom-field-type-selector')).toHaveAttribute('disabled'); }); it('submit data correctly', async () => { @@ -52,20 +52,19 @@ describe('FormFields ', () => { ); - userEvent.type(screen.getByTestId('custom-field-label-input'), 'hello'); - - fireEvent.change(screen.getByTestId('custom-field-type-selector'), { + fireEvent.change(await screen.findByTestId('custom-field-type-selector'), { target: { value: CustomFieldTypes.TOGGLE }, }); - userEvent.click(screen.getByText('Submit')); + userEvent.type(await screen.findByTestId('custom-field-label-input'), 'hello'); + userEvent.click(await screen.findByText('Submit')); await waitFor(() => { // data, isValid expect(onSubmit).toBeCalledWith( { label: 'hello', - type: 'toggle', + type: CustomFieldTypes.TOGGLE, }, true ); diff --git a/x-pack/plugins/cases/public/components/custom_fields/form_fields.tsx b/x-pack/plugins/cases/public/components/custom_fields/form_fields.tsx index 4ae51002e9587..5fe2e6db0839d 100644 --- a/x-pack/plugins/cases/public/components/custom_fields/form_fields.tsx +++ b/x-pack/plugins/cases/public/components/custom_fields/form_fields.tsx @@ -5,15 +5,15 @@ * 2.0. */ -import React, { memo, useCallback, useMemo, useState } from 'react'; -import { UseField } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; +import React, { memo, useMemo } from 'react'; +import { UseField, useFormData } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; import { TextField, SelectField, HiddenField, } from '@kbn/es-ui-shared-plugin/static/forms/components'; import type { EuiSelectOption } from '@elastic/eui'; -import { CustomFieldTypes } from '../../../common/types/domain'; +import type { CustomFieldTypes } from '../../../common/types/domain'; import { builderMap } from './builder'; interface FormFieldsProps { @@ -33,17 +33,10 @@ const fieldTypeSelectOptions = (): EuiSelectOption[] => { }; const FormFieldsComponent: React.FC = ({ isSubmitting, isEditMode }) => { - const [selectedType, setSelectedType] = useState(CustomFieldTypes.TEXT); - - const handleTypeChange = useCallback( - (e) => { - setSelectedType(e.target.value); - }, - [setSelectedType] - ); + const [{ type }] = useFormData<{ type: CustomFieldTypes }>(); const builtCustomField = useMemo(() => { - const builder = builderMap[selectedType]; + const builder = builderMap[type]; if (builder == null) { return null; @@ -52,7 +45,7 @@ const FormFieldsComponent: React.FC = ({ isSubmitting, isEditMo const customFieldBuilder = builder(); return customFieldBuilder.build(); - }, [selectedType]); + }, [type]); const Configure = builtCustomField?.Configure; const options = fieldTypeSelectOptions(); @@ -82,7 +75,6 @@ const FormFieldsComponent: React.FC = ({ isSubmitting, isEditMo isLoading: isSubmitting, disabled: isEditMode, }, - onChange: handleTypeChange, }} /> {Configure ? : null} 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 7864361063b31..15a280716c3c0 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, waitFor } from '@testing-library/react'; +import { screen } from '@testing-library/react'; import type { AppMockRenderer } from '../../common/mock'; import { createAppMockRenderer } from '../../common/mock'; @@ -34,38 +34,38 @@ describe('CustomFields', () => { jest.clearAllMocks(); }); - it('renders correctly', () => { + it('renders correctly', async () => { appMockRender.render(); - expect(screen.getByTestId('custom-fields-form-group')).toBeInTheDocument(); - expect(screen.getByTestId('add-custom-field')).toBeInTheDocument(); + expect(await screen.findByTestId('custom-fields-form-group')).toBeInTheDocument(); + expect(await screen.findByTestId('add-custom-field')).toBeInTheDocument(); }); - it('renders custom fields correctly', () => { + it('renders custom fields correctly', async () => { appMockRender.render( ); - expect(screen.getByTestId('add-custom-field')).toBeInTheDocument(); - expect(screen.getByTestId('custom-fields-list')).toBeInTheDocument(); + expect(await screen.findByTestId('add-custom-field')).toBeInTheDocument(); + expect(await screen.findByTestId('custom-fields-list')).toBeInTheDocument(); }); - it('renders loading state correctly', () => { + it('renders loading state correctly', async () => { appMockRender.render(); - expect(screen.getByRole('progressbar')).toBeInTheDocument(); + expect(await screen.findByRole('progressbar')).toBeInTheDocument(); }); - it('renders disabled state correctly', () => { + it('renders disabled state correctly', async () => { appMockRender.render(); - expect(screen.getByTestId('add-custom-field')).toHaveAttribute('disabled'); + expect(await screen.findByTestId('add-custom-field')).toHaveAttribute('disabled'); }); it('calls onChange on add option click', async () => { appMockRender.render(); - userEvent.click(screen.getByTestId('add-custom-field')); + userEvent.click(await screen.findByTestId('add-custom-field')); expect(props.handleAddCustomField).toBeCalled(); }); @@ -76,22 +76,22 @@ describe('CustomFields', () => { ); userEvent.click( - screen.getByTestId(`${customFieldsConfigurationMock[0].key}-custom-field-edit`) + await screen.findByTestId(`${customFieldsConfigurationMock[0].key}-custom-field-edit`) ); expect(props.handleEditCustomField).toBeCalledWith(customFieldsConfigurationMock[0].key); }); - it('shows the experimental badge', () => { + it('shows the experimental badge', async () => { appMockRender.render(); - expect(screen.getByTestId('case-experimental-badge')).toBeInTheDocument(); + expect(await screen.findByTestId('case-experimental-badge')).toBeInTheDocument(); }); it('shows error when custom fields reaches the limit', async () => { const generatedMockCustomFields = []; - for (let i = 0; i < 8; i++) { + for (let i = 0; i < 6; i++) { generatedMockCustomFields.push({ key: `field_key_${i + 1}`, label: `My custom label ${i + 1}`, @@ -103,11 +103,9 @@ describe('CustomFields', () => { appMockRender.render(); - userEvent.click(screen.getByTestId('add-custom-field')); + userEvent.click(await screen.findByTestId('add-custom-field')); - await waitFor(() => { - expect(screen.getByText(i18n.MAX_CUSTOM_FIELD_LIMIT(MAX_CUSTOM_FIELDS_PER_CASE))); - expect(screen.getByTestId('add-custom-field')).toHaveAttribute('disabled'); - }); + expect(await screen.findByText(i18n.MAX_CUSTOM_FIELD_LIMIT(MAX_CUSTOM_FIELDS_PER_CASE))); + expect(await screen.findByTestId('add-custom-field')).toHaveAttribute('disabled'); }); }); diff --git a/x-pack/plugins/cases/public/components/custom_fields/schema.tsx b/x-pack/plugins/cases/public/components/custom_fields/schema.tsx index 622ff1cb1673a..003e7126ef921 100644 --- a/x-pack/plugins/cases/public/components/custom_fields/schema.tsx +++ b/x-pack/plugins/cases/public/components/custom_fields/schema.tsx @@ -7,20 +7,10 @@ import { fieldValidators } from '@kbn/es-ui-shared-plugin/static/forms/helpers'; import * as i18n from './translations'; -import type { CustomFieldTypes } from '../../../common/types/domain'; import { MAX_CUSTOM_FIELD_LABEL_LENGTH } from '../../../common/constants'; const { emptyField, maxLengthField } = fieldValidators; -export interface CustomFieldsConfigurationFormProps { - key: string; - label: string; - type: CustomFieldTypes; - options?: { - required?: boolean; - }; -} - export const schema = { key: { validations: [ @@ -33,12 +23,15 @@ export const schema = { label: i18n.FIELD_LABEL, validations: [ { - validator: emptyField(i18n.REQUIRED_FIELD(i18n.FIELD_LABEL)), + validator: emptyField(i18n.REQUIRED_FIELD(i18n.FIELD_LABEL.toLocaleLowerCase())), }, { validator: maxLengthField({ length: MAX_CUSTOM_FIELD_LABEL_LENGTH, - message: i18n.MAX_LENGTH_ERROR('field label', MAX_CUSTOM_FIELD_LABEL_LENGTH), + message: i18n.MAX_LENGTH_ERROR( + i18n.FIELD_LABEL.toLocaleLowerCase(), + MAX_CUSTOM_FIELD_LABEL_LENGTH + ), }), }, ], @@ -51,4 +44,8 @@ export const schema = { }, ], }, + defaultValue: { + label: i18n.DEFAULT_VALUE, + validations: [], + }, }; diff --git a/x-pack/plugins/cases/public/components/custom_fields/text/config.ts b/x-pack/plugins/cases/public/components/custom_fields/text/config.ts index b318ebd1b3439..d8d8ef602dd1c 100644 --- a/x-pack/plugins/cases/public/components/custom_fields/text/config.ts +++ b/x-pack/plugins/cases/public/components/custom_fields/text/config.ts @@ -15,9 +15,11 @@ const { emptyField } = fieldValidators; export const getTextFieldConfig = ({ required, label, + defaultValue, }: { required: boolean; label: string; + defaultValue?: string | null; }): FieldConfig => { const validators = []; @@ -28,6 +30,7 @@ export const getTextFieldConfig = ({ } return { + ...(defaultValue && { defaultValue }), validations: [ ...validators, { diff --git a/x-pack/plugins/cases/public/components/custom_fields/text/configure.test.tsx b/x-pack/plugins/cases/public/components/custom_fields/text/configure.test.tsx index 4ca8cbc1e8663..5d9d7166db270 100644 --- a/x-pack/plugins/cases/public/components/custom_fields/text/configure.test.tsx +++ b/x-pack/plugins/cases/public/components/custom_fields/text/configure.test.tsx @@ -10,7 +10,6 @@ import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { FormTestComponent } from '../../../common/test_utils'; -import * as i18n from '../translations'; import { Configure } from './configure'; describe('Configure ', () => { @@ -27,27 +26,41 @@ describe('Configure ', () => { ); - expect(screen.getByText(i18n.FIELD_OPTION_REQUIRED)).toBeInTheDocument(); + expect(await screen.findByTestId('text-custom-field-required')).toBeInTheDocument(); }); - it('updates field options correctly', async () => { + it('updates field options correctly when not required', async () => { render( ); - userEvent.click(screen.getByText(i18n.FIELD_OPTION_REQUIRED)); + userEvent.click(await screen.findByTestId('form-test-component-submit-button')); - userEvent.click(screen.getByText('Submit')); + await waitFor(() => { + // data, isValid + expect(onSubmit).toBeCalledWith({}, true); + }); + }); + + it('updates field options correctly when required', async () => { + render( + + + + ); + + userEvent.click(await screen.findByTestId('text-custom-field-required')); + userEvent.paste(await screen.findByTestId('text-custom-field-default-value'), 'Default value'); + userEvent.click(await screen.findByTestId('form-test-component-submit-button')); await waitFor(() => { // data, isValid expect(onSubmit).toBeCalledWith( { - options: { - required: true, - }, + required: true, + defaultValue: 'Default value', }, true ); diff --git a/x-pack/plugins/cases/public/components/custom_fields/text/configure.tsx b/x-pack/plugins/cases/public/components/custom_fields/text/configure.tsx index 678c95c352173..1253640d91b79 100644 --- a/x-pack/plugins/cases/public/components/custom_fields/text/configure.tsx +++ b/x-pack/plugins/cases/public/components/custom_fields/text/configure.tsx @@ -6,27 +6,47 @@ */ import React from 'react'; -import { UseField } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; -import { CheckBoxField } from '@kbn/es-ui-shared-plugin/static/forms/components'; +import { UseField, useFormData } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; +import { CheckBoxField, TextField } from '@kbn/es-ui-shared-plugin/static/forms/components'; import type { CaseCustomFieldText } from '../../../../common/types/domain'; import type { CustomFieldType } from '../types'; +import { getTextFieldConfig } from './config'; import * as i18n from '../translations'; const ConfigureComponent: CustomFieldType['Configure'] = () => { + const [{ required }] = useFormData<{ required: boolean }>(); + const config = getTextFieldConfig({ + required: false, + label: i18n.DEFAULT_VALUE.toLocaleLowerCase(), + }); + return ( <> + {required && ( + + )} ); }; diff --git a/x-pack/plugins/cases/public/components/custom_fields/text/create.test.tsx b/x-pack/plugins/cases/public/components/custom_fields/text/create.test.tsx index 1d1768a7c1c7c..9db8541993057 100644 --- a/x-pack/plugins/cases/public/components/custom_fields/text/create.test.tsx +++ b/x-pack/plugins/cases/public/components/custom_fields/text/create.test.tsx @@ -21,19 +21,36 @@ describe('Create ', () => { jest.clearAllMocks(); }); + // required text custom field with a default value const customFieldConfiguration = customFieldsConfigurationMock[0]; - it('renders correctly', async () => { + it('renders correctly with default values', async () => { render( ); - expect(screen.getByText(customFieldConfiguration.label)).toBeInTheDocument(); + expect(await screen.findByText(customFieldConfiguration.label)).toBeInTheDocument(); + expect( - screen.getByTestId(`${customFieldConfiguration.key}-text-create-custom-field`) - ).toBeInTheDocument(); + await screen.findByTestId(`${customFieldConfiguration.key}-text-create-custom-field`) + ).toHaveValue(customFieldConfiguration.defaultValue as string); + }); + + it('renders correctly with optional fields', async () => { + const optionalField = customFieldsConfigurationMock[2]; // optional text custom field + + render( + + + + ); + + expect(await screen.findByText(optionalField.label)).toBeInTheDocument(); + expect(await screen.findByTestId(`${optionalField.key}-text-create-custom-field`)).toHaveValue( + '' + ); }); it('renders loading state correctly', async () => { @@ -43,7 +60,7 @@ describe('Create ', () => { ); - expect(screen.getByRole('progressbar')).toBeInTheDocument(); + expect(await screen.findByRole('progressbar')).toBeInTheDocument(); }); it('disables the text when loading', async () => { @@ -54,7 +71,7 @@ describe('Create ', () => { ); expect( - screen.getByTestId(`${customFieldConfiguration.key}-text-create-custom-field`) + await screen.findByTestId(`${customFieldConfiguration.key}-text-create-custom-field`) ).toHaveAttribute('disabled'); }); @@ -65,12 +82,13 @@ describe('Create ', () => { ); - userEvent.type( - screen.getByTestId(`${customFieldConfiguration.key}-text-create-custom-field`), - 'this is a sample text!' + const textCustomField = await screen.findByTestId( + `${customFieldConfiguration.key}-text-create-custom-field` ); - userEvent.click(screen.getByText('Submit')); + userEvent.clear(textCustomField); + userEvent.paste(textCustomField, 'this is a sample text!'); + userEvent.click(await screen.findByText('Submit')); await waitFor(() => { // data, isValid @@ -95,18 +113,19 @@ describe('Create ', () => { const sampleText = 'a'.repeat(MAX_CUSTOM_FIELD_TEXT_VALUE_LENGTH + 1); userEvent.paste( - screen.getByTestId(`${customFieldConfiguration.key}-text-create-custom-field`), + await screen.findByTestId(`${customFieldConfiguration.key}-text-create-custom-field`), sampleText ); - userEvent.click(screen.getByText('Submit')); + userEvent.click(await screen.findByText('Submit')); + + expect( + await screen.findByText( + `The length of the ${customFieldConfiguration.label} is too long. The maximum length is ${MAX_CUSTOM_FIELD_TEXT_VALUE_LENGTH} characters.` + ) + ).toBeInTheDocument(); await waitFor(() => { - expect( - screen.getByText( - `The length of the ${customFieldConfiguration.label} is too long. The maximum length is ${MAX_CUSTOM_FIELD_TEXT_VALUE_LENGTH} characters.` - ) - ).toBeInTheDocument(); expect(onSubmit).toHaveBeenCalledWith({}, false); }); }); @@ -124,18 +143,18 @@ describe('Create ', () => { const sampleText = 'a'.repeat(MAX_CUSTOM_FIELD_TEXT_VALUE_LENGTH + 1); userEvent.paste( - screen.getByTestId(`${customFieldConfiguration.key}-text-create-custom-field`), + await screen.findByTestId(`${customFieldConfiguration.key}-text-create-custom-field`), sampleText ); + userEvent.click(await screen.findByText('Submit')); - userEvent.click(screen.getByText('Submit')); + expect( + await screen.findByText( + `The length of the ${customFieldConfiguration.label} is too long. The maximum length is ${MAX_CUSTOM_FIELD_TEXT_VALUE_LENGTH} characters.` + ) + ).toBeInTheDocument(); await waitFor(() => { - expect( - screen.getByText( - `The length of the ${customFieldConfiguration.label} is too long. The maximum length is ${MAX_CUSTOM_FIELD_TEXT_VALUE_LENGTH} characters.` - ) - ).toBeInTheDocument(); expect(onSubmit).toHaveBeenCalledWith({}, false); }); }); @@ -150,17 +169,16 @@ describe('Create ', () => { ); - userEvent.paste( - screen.getByTestId(`${customFieldConfiguration.key}-text-create-custom-field`), - '' + userEvent.clear( + await screen.findByTestId(`${customFieldConfiguration.key}-text-create-custom-field`) ); + userEvent.click(await screen.findByText('Submit')); - userEvent.click(screen.getByText('Submit')); + expect( + await screen.findByText(`A ${customFieldConfiguration.label} is required.`) + ).toBeInTheDocument(); await waitFor(() => { - expect( - screen.getByText(`${customFieldConfiguration.label} is required.`) - ).toBeInTheDocument(); expect(onSubmit).toHaveBeenCalledWith({}, false); }); }); @@ -170,12 +188,17 @@ describe('Create ', () => { ); - userEvent.click(screen.getByText('Submit')); + userEvent.click(await screen.findByText('Submit')); await waitFor(() => { expect(onSubmit).toHaveBeenCalledWith({}, true); diff --git a/x-pack/plugins/cases/public/components/custom_fields/text/create.tsx b/x-pack/plugins/cases/public/components/custom_fields/text/create.tsx index 195e55073dd71..aaab2043fb332 100644 --- a/x-pack/plugins/cases/public/components/custom_fields/text/create.tsx +++ b/x-pack/plugins/cases/public/components/custom_fields/text/create.tsx @@ -16,8 +16,12 @@ const CreateComponent: CustomFieldType['Create'] = ({ customFieldConfiguration, isLoading, }) => { - const { key, label, required } = customFieldConfiguration; - const config = getTextFieldConfig({ required, label }); + const { key, label, required, defaultValue } = customFieldConfiguration; + const config = getTextFieldConfig({ + required, + label, + ...(defaultValue && { defaultValue: String(defaultValue) }), + }); return ( { const onSubmit = jest.fn(); @@ -38,10 +39,12 @@ describe('Edit ', () => { ); - expect(screen.getByTestId('case-text-custom-field-test_key_1')).toBeInTheDocument(); - expect(screen.getByTestId('case-text-custom-field-edit-button-test_key_1')).toBeInTheDocument(); - expect(screen.getByText(customFieldConfiguration.label)).toBeInTheDocument(); - expect(screen.getByText('My text test value 1')).toBeInTheDocument(); + expect(await screen.findByTestId('case-text-custom-field-test_key_1')).toBeInTheDocument(); + expect( + await screen.findByTestId('case-text-custom-field-edit-button-test_key_1') + ).toBeInTheDocument(); + expect(await screen.findByText(customFieldConfiguration.label)).toBeInTheDocument(); + expect(await screen.findByText('My text test value 1')).toBeInTheDocument(); }); it('does not shows the edit button if the user does not have permissions', async () => { @@ -93,7 +96,9 @@ describe('Edit ', () => { ); - expect(screen.getByTestId('case-text-custom-field-loading-test_key_1')).toBeInTheDocument(); + expect( + await screen.findByTestId('case-text-custom-field-loading-test_key_1') + ).toBeInTheDocument(); }); it('shows the no value text if the custom field is undefined', async () => { @@ -108,10 +113,10 @@ describe('Edit ', () => { ); - expect(screen.getByText('No value is added')).toBeInTheDocument(); + expect(await screen.findByText('No value is added')).toBeInTheDocument(); }); - it('shows the no value text if the the value is null', async () => { + it('uses the required value correctly if a required field is empty', async () => { render( { ); - expect(screen.getByText('No value is added')).toBeInTheDocument(); + expect(await screen.findByText('No value is added')).toBeInTheDocument(); + userEvent.click(await screen.findByTestId('case-text-custom-field-edit-button-test_key_1')); + + expect( + await screen.findByTestId(`case-text-custom-field-form-field-${customFieldConfiguration.key}`) + ).toHaveValue(customFieldConfiguration.defaultValue as string); + expect( + await screen.findByText('This field is populated with the default value.') + ).toBeInTheDocument(); + + userEvent.click(await screen.findByTestId('case-text-custom-field-submit-button-test_key_1')); + + await waitFor(() => { + expect(onSubmit).toBeCalledWith({ + ...customField, + value: customFieldConfiguration.defaultValue, + }); + }); }); it('does not show the value when the custom field is undefined', async () => { @@ -195,21 +217,58 @@ describe('Edit ', () => { ); - userEvent.click(screen.getByTestId('case-text-custom-field-edit-button-test_key_1')); - userEvent.paste(screen.getByTestId('case-text-custom-field-form-field-test_key_1'), '!!!'); + userEvent.click(await screen.findByTestId('case-text-custom-field-edit-button-test_key_1')); + userEvent.paste( + await screen.findByTestId('case-text-custom-field-form-field-test_key_1'), + '!!!' + ); + + expect( + await screen.findByTestId('case-text-custom-field-submit-button-test_key_1') + ).not.toBeDisabled(); + + userEvent.click(await screen.findByTestId('case-text-custom-field-submit-button-test_key_1')); await waitFor(() => { - expect( - screen.getByTestId('case-text-custom-field-submit-button-test_key_1') - ).not.toBeDisabled(); + expect(onSubmit).toBeCalledWith({ + ...customField, + value: 'My text test value 1!!!', + }); }); + }); - userEvent.click(screen.getByTestId('case-text-custom-field-submit-button-test_key_1')); + it('calls onSubmit with defaultValue if no initialValue exists', async () => { + render( + + + + ); + + userEvent.click(await screen.findByTestId('case-text-custom-field-edit-button-test_key_1')); + + expect(await screen.findByText(POPULATED_WITH_DEFAULT)).toBeInTheDocument(); + expect(await screen.findByTestId('case-text-custom-field-form-field-test_key_1')).toHaveValue( + customFieldConfiguration.defaultValue as string + ); + expect( + await screen.findByTestId('case-text-custom-field-submit-button-test_key_1') + ).not.toBeDisabled(); + + userEvent.click(await screen.findByTestId('case-text-custom-field-submit-button-test_key_1')); await waitFor(() => { expect(onSubmit).toBeCalledWith({ ...customField, - value: 'My text test value 1!!!', + value: customFieldConfiguration.defaultValue, }); }); }); @@ -227,16 +286,14 @@ describe('Edit ', () => { ); - userEvent.click(screen.getByTestId('case-text-custom-field-edit-button-test_key_1')); - userEvent.clear(screen.getByTestId('case-text-custom-field-form-field-test_key_1')); + userEvent.click(await screen.findByTestId('case-text-custom-field-edit-button-test_key_1')); + userEvent.clear(await screen.findByTestId('case-text-custom-field-form-field-test_key_1')); - await waitFor(() => { - expect( - screen.getByTestId('case-text-custom-field-submit-button-test_key_1') - ).not.toBeDisabled(); - }); + expect( + await screen.findByTestId('case-text-custom-field-submit-button-test_key_1') + ).not.toBeDisabled(); - userEvent.click(screen.getByTestId('case-text-custom-field-submit-button-test_key_1')); + userEvent.click(await screen.findByTestId('case-text-custom-field-submit-button-test_key_1')); await waitFor(() => { expect(onSubmit).toBeCalledWith({ @@ -259,11 +316,13 @@ describe('Edit ', () => { ); - userEvent.click(screen.getByTestId('case-text-custom-field-edit-button-test_key_1')); + userEvent.click(await screen.findByTestId('case-text-custom-field-edit-button-test_key_1')); - expect(screen.getByTestId('case-text-custom-field-form-field-test_key_1')).toBeInTheDocument(); + expect( + await screen.findByTestId('case-text-custom-field-form-field-test_key_1') + ).toBeInTheDocument(); - userEvent.click(screen.getByTestId('case-text-custom-field-cancel-button-test_key_1')); + userEvent.click(await screen.findByTestId('case-text-custom-field-cancel-button-test_key_1')); expect( screen.queryByTestId('case-text-custom-field-form-field-test_key_1') @@ -283,23 +342,24 @@ describe('Edit ', () => { ); - userEvent.click(screen.getByTestId('case-text-custom-field-edit-button-test_key_1')); - userEvent.paste(screen.getByTestId('case-text-custom-field-form-field-test_key_1'), '!!!'); + userEvent.click(await screen.findByTestId('case-text-custom-field-edit-button-test_key_1')); + userEvent.paste( + await screen.findByTestId('case-text-custom-field-form-field-test_key_1'), + '!!!' + ); - await waitFor(() => { - expect( - screen.getByTestId('case-text-custom-field-submit-button-test_key_1') - ).not.toBeDisabled(); - }); + expect( + await screen.findByTestId('case-text-custom-field-submit-button-test_key_1') + ).not.toBeDisabled(); - userEvent.click(screen.getByTestId('case-text-custom-field-cancel-button-test_key_1')); + userEvent.click(await screen.findByTestId('case-text-custom-field-cancel-button-test_key_1')); expect( screen.queryByTestId('case-text-custom-field-form-field-test_key_1') ).not.toBeInTheDocument(); - userEvent.click(screen.getByTestId('case-text-custom-field-edit-button-test_key_1')); - expect(screen.getByTestId('case-text-custom-field-form-field-test_key_1')).toHaveValue( + userEvent.click(await screen.findByTestId('case-text-custom-field-edit-button-test_key_1')); + expect(await screen.findByTestId('case-text-custom-field-form-field-test_key_1')).toHaveValue( 'My text test value 1' ); }); @@ -317,12 +377,10 @@ describe('Edit ', () => { ); - userEvent.click(screen.getByTestId('case-text-custom-field-edit-button-test_key_1')); - userEvent.clear(screen.getByTestId('case-text-custom-field-form-field-test_key_1')); + userEvent.click(await screen.findByTestId('case-text-custom-field-edit-button-test_key_1')); + userEvent.clear(await screen.findByTestId('case-text-custom-field-form-field-test_key_1')); - await waitFor(() => { - expect(screen.getByText('My test label 1 is required.')).toBeInTheDocument(); - }); + expect(await screen.findByText('A My test label 1 is required.')).toBeInTheDocument(); }); it('does not shows a validation error if the field is not required', async () => { @@ -338,14 +396,12 @@ describe('Edit ', () => { ); - userEvent.click(screen.getByTestId('case-text-custom-field-edit-button-test_key_1')); - userEvent.clear(screen.getByTestId('case-text-custom-field-form-field-test_key_1')); + userEvent.click(await screen.findByTestId('case-text-custom-field-edit-button-test_key_1')); + userEvent.clear(await screen.findByTestId('case-text-custom-field-form-field-test_key_1')); - await waitFor(() => { - expect( - screen.getByTestId('case-text-custom-field-submit-button-test_key_1') - ).not.toBeDisabled(); - }); + expect( + await screen.findByTestId('case-text-custom-field-submit-button-test_key_1') + ).not.toBeDisabled(); expect(screen.queryByText('My test label 1 is required.')).not.toBeInTheDocument(); }); @@ -363,19 +419,17 @@ describe('Edit ', () => { ); - userEvent.click(screen.getByTestId('case-text-custom-field-edit-button-test_key_1')); - userEvent.clear(screen.getByTestId('case-text-custom-field-form-field-test_key_1')); + userEvent.click(await screen.findByTestId('case-text-custom-field-edit-button-test_key_1')); + userEvent.clear(await screen.findByTestId('case-text-custom-field-form-field-test_key_1')); userEvent.paste( - screen.getByTestId('case-text-custom-field-form-field-test_key_1'), + await screen.findByTestId('case-text-custom-field-form-field-test_key_1'), 'a'.repeat(MAX_CUSTOM_FIELD_TEXT_VALUE_LENGTH + 1) ); - await waitFor(() => { - expect( - screen.getByText( - `The length of the My test label 1 is too long. The maximum length is ${MAX_CUSTOM_FIELD_TEXT_VALUE_LENGTH} characters.` - ) - ).toBeInTheDocument(); - }); + expect( + await screen.findByText( + `The length of the My test label 1 is too long. The maximum length is ${MAX_CUSTOM_FIELD_TEXT_VALUE_LENGTH} characters.` + ) + ).toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/cases/public/components/custom_fields/text/edit.tsx b/x-pack/plugins/cases/public/components/custom_fields/text/edit.tsx index 58ba4af80255d..4fc7c2dde9af4 100644 --- a/x-pack/plugins/cases/public/components/custom_fields/text/edit.tsx +++ b/x-pack/plugins/cases/public/components/custom_fields/text/edit.tsx @@ -18,17 +18,29 @@ import { EuiText, } from '@elastic/eui'; import type { FormHook } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; -import { useForm, UseField, Form } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; +import { + useForm, + UseField, + Form, + useFormData, +} from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; import { TextField } from '@kbn/es-ui-shared-plugin/static/forms/components'; import type { CaseCustomFieldText } from '../../../../common/types/domain'; import { CustomFieldTypes } from '../../../../common/types/domain'; import type { CasesConfigurationUICustomField } from '../../../../common/ui'; import type { CustomFieldType } from '../types'; import { View } from './view'; -import { CANCEL, EDIT_CUSTOM_FIELDS_ARIA_LABEL, NO_CUSTOM_FIELD_SET, SAVE } from '../translations'; +import { + CANCEL, + EDIT_CUSTOM_FIELDS_ARIA_LABEL, + NO_CUSTOM_FIELD_SET, + SAVE, + POPULATED_WITH_DEFAULT, +} from '../translations'; import { getTextFieldConfig } from './config'; interface FormState { + value: string; isValid: boolean | undefined; submit: FormHook<{ value: string }>['submit']; } @@ -46,20 +58,30 @@ const FormWrapperComponent: React.FC = ({ isLoading, onChange, }) => { - const { form } = useForm({ - defaultValue: { value: initialValue }, + const { form } = useForm<{ value: string }>({ + defaultValue: { + value: + customFieldConfiguration?.defaultValue != null && isEmpty(initialValue) + ? String(customFieldConfiguration.defaultValue) + : initialValue, + }, }); - - const { submit, isValid: isFormValid } = form; - - useEffect(() => { - onChange({ isValid: isFormValid, submit }); - }, [isFormValid, onChange, submit]); - + const [{ value }] = useFormData({ form }); + const { submit, isValid } = form; const formFieldConfig = getTextFieldConfig({ required: customFieldConfiguration.required, label: customFieldConfiguration.label, }); + const populatedWithDefault = + value === customFieldConfiguration?.defaultValue && isEmpty(initialValue); + + useEffect(() => { + onChange({ + value, + isValid, + submit, + }); + }, [isValid, onChange, submit, value]); return (
@@ -67,6 +89,7 @@ const FormWrapperComponent: React.FC = ({ path="value" config={formFieldConfig} component={TextField} + helpText={populatedWithDefault && POPULATED_WITH_DEFAULT} componentProps={{ euiFieldProps: { fullWidth: true, @@ -89,11 +112,12 @@ const EditComponent: CustomFieldType['Edit'] = ({ isLoading, canUpdate, }) => { + const initialValue = customField?.value ?? ''; const [isEdit, setIsEdit] = useState(false); - const [formState, setFormState] = useState({ isValid: undefined, submit: async () => ({ isValid: false, data: { value: '' } }), + value: initialValue, }); const onEdit = () => { @@ -121,9 +145,10 @@ const EditComponent: CustomFieldType['Edit'] = ({ setIsEdit(false); }; - const initialValue = customField?.value ?? ''; const title = customFieldConfiguration.label; - const isTextFieldValid = formState.isValid; + const isTextFieldValid = + formState.isValid || + (formState.value === customFieldConfiguration.defaultValue && !initialValue); const isCustomFieldValueDefined = !isEmpty(customField?.value); return ( diff --git a/x-pack/plugins/cases/public/components/custom_fields/toggle/configure.test.tsx b/x-pack/plugins/cases/public/components/custom_fields/toggle/configure.test.tsx index 4ca8cbc1e8663..8153ca64a789c 100644 --- a/x-pack/plugins/cases/public/components/custom_fields/toggle/configure.test.tsx +++ b/x-pack/plugins/cases/public/components/custom_fields/toggle/configure.test.tsx @@ -30,24 +30,60 @@ describe('Configure ', () => { expect(screen.getByText(i18n.FIELD_OPTION_REQUIRED)).toBeInTheDocument(); }); - it('updates field options correctly', async () => { + it('updates field options correctly when not required', async () => { render( ); - userEvent.click(screen.getByText(i18n.FIELD_OPTION_REQUIRED)); + userEvent.click(await screen.findByTestId('form-test-component-submit-button')); - userEvent.click(screen.getByText('Submit')); + await waitFor(() => { + // data, isValid + expect(onSubmit).toBeCalledWith({}, true); + }); + }); + + it('updates field options correctly when required', async () => { + render( + + + + ); + + userEvent.click(await screen.findByTestId('toggle-custom-field-required')); + userEvent.click(await screen.findByTestId('toggle-custom-field-default-value')); + userEvent.click(await screen.findByTestId('form-test-component-submit-button')); + + await waitFor(() => { + // data, isValid + expect(onSubmit).toBeCalledWith( + { + required: true, + defaultValue: true, + }, + true + ); + }); + }); + + it('default value is "false" when required', async () => { + render( + + + + ); + + userEvent.click(await screen.findByTestId('toggle-custom-field-required')); + userEvent.click(await screen.findByTestId('form-test-component-submit-button')); await waitFor(() => { // data, isValid expect(onSubmit).toBeCalledWith( { - options: { - required: true, - }, + required: true, + defaultValue: false, }, true ); diff --git a/x-pack/plugins/cases/public/components/custom_fields/toggle/configure.tsx b/x-pack/plugins/cases/public/components/custom_fields/toggle/configure.tsx index a363d8c35eb45..83645ae185f1d 100644 --- a/x-pack/plugins/cases/public/components/custom_fields/toggle/configure.tsx +++ b/x-pack/plugins/cases/public/components/custom_fields/toggle/configure.tsx @@ -6,26 +6,42 @@ */ import React from 'react'; -import { UseField } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; -import { CheckBoxField } from '@kbn/es-ui-shared-plugin/static/forms/components'; +import { UseField, useFormData } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; +import { CheckBoxField, ToggleField } from '@kbn/es-ui-shared-plugin/static/forms/components'; import type { CaseCustomFieldToggle } from '../../../../common/types/domain'; import type { CustomFieldType } from '../types'; import * as i18n from '../translations'; const ConfigureComponent: CustomFieldType['Configure'] = () => { + const [{ required }] = useFormData<{ required: boolean }>(); + return ( <> + {required && ( + + )} ); }; diff --git a/x-pack/plugins/cases/public/components/custom_fields/toggle/create.test.tsx b/x-pack/plugins/cases/public/components/custom_fields/toggle/create.test.tsx index 3a09b3f5b17cb..9672b3c8bb6be 100644 --- a/x-pack/plugins/cases/public/components/custom_fields/toggle/create.test.tsx +++ b/x-pack/plugins/cases/public/components/custom_fields/toggle/create.test.tsx @@ -22,18 +22,18 @@ describe('Create ', () => { const customFieldConfiguration = customFieldsConfigurationMock[1]; - it('renders correctly', async () => { + it('renders correctly with required and defaultValue', async () => { render( ); - expect(screen.getByText(customFieldConfiguration.label)).toBeInTheDocument(); + expect(await screen.findByText(customFieldConfiguration.label)).toBeInTheDocument(); expect( - screen.getByTestId(`${customFieldConfiguration.key}-toggle-create-custom-field`) + await screen.findByTestId(`${customFieldConfiguration.key}-toggle-create-custom-field`) ).toBeInTheDocument(); - expect(screen.getByRole('switch')).not.toBeChecked(); + expect(await screen.findByRole('switch')).toBeChecked(); // defaultValue true }); it('updates the value correctly', async () => { @@ -43,16 +43,15 @@ describe('Create ', () => { ); - userEvent.click(screen.getByRole('switch')); - - userEvent.click(screen.getByText('Submit')); + userEvent.click(await screen.findByRole('switch')); + userEvent.click(await screen.findByText('Submit')); await waitFor(() => { // data, isValid expect(onSubmit).toHaveBeenCalledWith( { customFields: { - [customFieldConfiguration.key]: true, + [customFieldConfiguration.key]: false, }, }, true @@ -60,17 +59,22 @@ describe('Create ', () => { }); }); - it('sets value to false by default', async () => { + it('sets value to false by default when there is no defaultValue configured', async () => { render( ); - userEvent.click(screen.getByText('Submit')); + userEvent.click(await screen.findByText('Submit')); await waitFor(() => { expect(onSubmit).toHaveBeenCalledWith( @@ -91,6 +95,6 @@ describe('Create ', () => { ); - expect(screen.getByRole('switch')).toBeDisabled(); + expect(await screen.findByRole('switch')).toBeDisabled(); }); }); diff --git a/x-pack/plugins/cases/public/components/custom_fields/toggle/create.tsx b/x-pack/plugins/cases/public/components/custom_fields/toggle/create.tsx index 169d3f48d6710..2d3f51bc4f678 100644 --- a/x-pack/plugins/cases/public/components/custom_fields/toggle/create.tsx +++ b/x-pack/plugins/cases/public/components/custom_fields/toggle/create.tsx @@ -15,13 +15,13 @@ const CreateComponent: CustomFieldType['Create'] = ({ customFieldConfiguration, isLoading, }) => { - const { key, label } = customFieldConfiguration; + const { key, label, defaultValue } = customFieldConfiguration; return ( i18n.translate('xpack.cases.customFields.requiredField', { values: { fieldName }, - defaultMessage: '{fieldName} is required.', + defaultMessage: 'A {fieldName} is required.', }); export const EDIT_CUSTOM_FIELDS_ARIA_LABEL = (customFieldLabel: string) => @@ -116,3 +120,10 @@ export const TOGGLE_FIELD_OFF_LABEL = i18n.translate( defaultMessage: 'Off', } ); + +export const POPULATED_WITH_DEFAULT = i18n.translate( + 'xpack.cases.customFields.fieldOptions.populatedWithDefault', + { + defaultMessage: 'This field is populated with the default value.', + } +); diff --git a/x-pack/plugins/cases/public/components/custom_fields/utils.test.ts b/x-pack/plugins/cases/public/components/custom_fields/utils.test.ts index 9bc783a36a53b..ba629a6ea10a4 100644 --- a/x-pack/plugins/cases/public/components/custom_fields/utils.test.ts +++ b/x-pack/plugins/cases/public/components/custom_fields/utils.test.ts @@ -5,138 +5,288 @@ * 2.0. */ -import { addOrReplaceCustomField } from './utils'; +import { addOrReplaceCustomField, customFieldSerializer } from './utils'; import { customFieldsConfigurationMock, customFieldsMock } from '../../containers/mock'; +import type { CustomFieldConfiguration } from '../../../common/types/domain'; import { CustomFieldTypes } from '../../../common/types/domain'; import type { CaseUICustomField } from '../../../common/ui'; -describe('addOrReplaceCustomField ', () => { - beforeEach(() => { - jest.clearAllMocks(); +describe('utils ', () => { + describe('addOrReplaceCustomField ', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('adds new custom field correctly', async () => { + const fieldToAdd: CaseUICustomField = { + key: 'my_test_key', + type: CustomFieldTypes.TEXT, + value: 'my_test_value', + }; + const res = addOrReplaceCustomField(customFieldsMock, fieldToAdd); + expect(res).toMatchInlineSnapshot( + [...customFieldsMock, fieldToAdd], + ` + Array [ + Object { + "key": "test_key_1", + "type": "text", + "value": "My text test value 1", + }, + Object { + "key": "test_key_2", + "type": "toggle", + "value": true, + }, + Object { + "key": "test_key_3", + "type": "text", + "value": null, + }, + Object { + "key": "test_key_4", + "type": "toggle", + "value": null, + }, + Object { + "key": "my_test_key", + "type": "text", + "value": "my_test_value", + }, + ] + ` + ); + }); + + it('updates existing custom field correctly', async () => { + const fieldToUpdate = { + ...customFieldsMock[0], + field: { value: ['My text test value 1!!!'] }, + }; + + const res = addOrReplaceCustomField(customFieldsMock, fieldToUpdate as CaseUICustomField); + expect(res).toMatchInlineSnapshot( + [ + { ...fieldToUpdate }, + { ...customFieldsMock[1] }, + { ...customFieldsMock[2] }, + { ...customFieldsMock[3] }, + ], + ` + Array [ + Object { + "field": Object { + "value": Array [ + "My text test value 1!!!", + ], + }, + "key": "test_key_1", + "type": "text", + "value": "My text test value 1", + }, + Object { + "key": "test_key_2", + "type": "toggle", + "value": true, + }, + Object { + "key": "test_key_3", + "type": "text", + "value": null, + }, + Object { + "key": "test_key_4", + "type": "toggle", + "value": null, + }, + ] + ` + ); + }); + + it('adds new custom field configuration correctly', async () => { + const fieldToAdd = { + key: 'my_test_key', + type: CustomFieldTypes.TEXT, + label: 'my_test_label', + required: true, + }; + const res = addOrReplaceCustomField(customFieldsConfigurationMock, fieldToAdd); + expect(res).toMatchInlineSnapshot( + [...customFieldsConfigurationMock, fieldToAdd], + ` + Array [ + Object { + "defaultValue": "My default value", + "key": "test_key_1", + "label": "My test label 1", + "required": true, + "type": "text", + }, + Object { + "defaultValue": true, + "key": "test_key_2", + "label": "My test label 2", + "required": true, + "type": "toggle", + }, + Object { + "key": "test_key_3", + "label": "My test label 3", + "required": false, + "type": "text", + }, + Object { + "key": "test_key_4", + "label": "My test label 4", + "required": false, + "type": "toggle", + }, + Object { + "key": "my_test_key", + "label": "my_test_label", + "required": true, + "type": "text", + }, + ] + ` + ); + }); + + it('updates existing custom field config correctly', async () => { + const fieldToUpdate = { + ...customFieldsConfigurationMock[0], + label: `${customFieldsConfigurationMock[0].label}!!!`, + }; + + const res = addOrReplaceCustomField(customFieldsConfigurationMock, fieldToUpdate); + expect(res).toMatchInlineSnapshot( + [ + { ...fieldToUpdate }, + { ...customFieldsConfigurationMock[1] }, + { ...customFieldsConfigurationMock[2] }, + { ...customFieldsConfigurationMock[3] }, + ], + ` + Array [ + Object { + "defaultValue": "My default value", + "key": "test_key_1", + "label": "My test label 1!!!", + "required": true, + "type": "text", + }, + Object { + "defaultValue": true, + "key": "test_key_2", + "label": "My test label 2", + "required": true, + "type": "toggle", + }, + Object { + "key": "test_key_3", + "label": "My test label 3", + "required": false, + "type": "text", + }, + Object { + "key": "test_key_4", + "label": "My test label 4", + "required": false, + "type": "toggle", + }, + ] + ` + ); + }); }); - it('adds new custom field correctly', async () => { - const fieldToAdd: CaseUICustomField = { - key: 'my_test_key', - type: CustomFieldTypes.TEXT, - value: 'my_test_value', - }; - const res = addOrReplaceCustomField(customFieldsMock, fieldToAdd); - expect(res).toMatchInlineSnapshot( - [...customFieldsMock, fieldToAdd], - ` - Array [ - Object { - "key": "test_key_1", - "type": "text", - "value": "My text test value 1", - }, - Object { - "key": "test_key_2", - "type": "toggle", - "value": true, - }, + describe('customFieldSerializer ', () => { + it('serializes the data correctly if the default value is a normal string', async () => { + const customField = { + key: 'my_test_key', + type: CustomFieldTypes.TEXT, + required: true, + defaultValue: 'foobar', + } as CustomFieldConfiguration; + + expect(customFieldSerializer(customField)).toMatchInlineSnapshot(` Object { + "defaultValue": "foobar", "key": "my_test_key", + "required": true, "type": "text", - "value": "my_test_value", - }, - ] - ` - ); - }); + } + `); + }); - it('updates existing custom field correctly', async () => { - const fieldToUpdate = { - ...customFieldsMock[0], - field: { value: ['My text test value 1!!!'] }, - }; - - const res = addOrReplaceCustomField(customFieldsMock, fieldToUpdate as CaseUICustomField); - expect(res).toMatchInlineSnapshot( - [{ ...fieldToUpdate }, { ...customFieldsMock[1] }], - ` - Array [ - Object { - "field": Object { - "value": Array [ - "My text test value 1!!!", - ], - }, - "key": "test_key_1", - "type": "text", - "value": "My text test value 1", - }, - Object { - "key": "test_key_2", - "type": "toggle", - "value": true, - }, - ] - ` - ); - }); + it('serializes the data correctly if the default value is undefined', async () => { + const customField = { + key: 'my_test_key', + type: CustomFieldTypes.TEXT, + required: true, + } as CustomFieldConfiguration; - it('adds new custom field configuration correctly', async () => { - const fieldToAdd = { - key: 'my_test_key', - type: CustomFieldTypes.TEXT, - label: 'my_test_label', - required: true, - }; - const res = addOrReplaceCustomField(customFieldsConfigurationMock, fieldToAdd); - expect(res).toMatchInlineSnapshot( - [...customFieldsConfigurationMock, fieldToAdd], - ` - Array [ + expect(customFieldSerializer(customField)).toMatchInlineSnapshot(` Object { - "key": "test_key_1", - "label": "My test label 1", + "key": "my_test_key", "required": true, "type": "text", - }, - Object { - "key": "test_key_2", - "label": "My test label 2", - "required": false, - "type": "toggle", - }, + } + `); + }); + + it('serializes the data correctly if the default value is null', async () => { + const customField = { + key: 'my_test_key', + type: CustomFieldTypes.TEXT, + required: true, + defaultValue: null, + } as CustomFieldConfiguration; + + expect(customFieldSerializer(customField)).toMatchInlineSnapshot(` Object { + "defaultValue": null, "key": "my_test_key", - "label": "my_test_label", "required": true, "type": "text", - }, - ] - ` - ); - }); + } + `); + }); + + it('serializes the data correctly if the default value is an empty string', async () => { + const customField = { + key: 'my_test_key', + type: CustomFieldTypes.TEXT, + required: true, + defaultValue: ' ', + } as CustomFieldConfiguration; - it('updates existing custom field config correctly', async () => { - const fieldToUpdate = { - ...customFieldsConfigurationMock[0], - label: `${customFieldsConfigurationMock[0].label}!!!`, - }; - - const res = addOrReplaceCustomField(customFieldsConfigurationMock, fieldToUpdate); - expect(res).toMatchInlineSnapshot( - [{ ...fieldToUpdate }, { ...customFieldsConfigurationMock[1] }], - ` - Array [ + expect(customFieldSerializer(customField)).toMatchInlineSnapshot(` Object { - "key": "test_key_1", - "label": "My test label 1!!!", + "key": "my_test_key", "required": true, "type": "text", - }, + } + `); + }); + + it('serializes the data correctly if the default value is false', async () => { + const customField = { + key: 'my_test_key', + type: CustomFieldTypes.TOGGLE, + required: true, + defaultValue: false, + } as CustomFieldConfiguration; + + expect(customFieldSerializer(customField)).toMatchInlineSnapshot(` Object { - "key": "test_key_2", - "label": "My test label 2", - "required": false, + "defaultValue": false, + "key": "my_test_key", + "required": true, "type": "toggle", - }, - ] - ` - ); + } + `); + }); }); }); diff --git a/x-pack/plugins/cases/public/components/custom_fields/utils.ts b/x-pack/plugins/cases/public/components/custom_fields/utils.ts index 18906f338fc42..bea01a3761bd0 100644 --- a/x-pack/plugins/cases/public/components/custom_fields/utils.ts +++ b/x-pack/plugins/cases/public/components/custom_fields/utils.ts @@ -5,6 +5,10 @@ * 2.0. */ +import { isEmptyString } from '@kbn/es-ui-shared-plugin/static/validators/string'; +import { isString } from 'lodash'; +import type { CustomFieldConfiguration } from '../../../common/types/domain'; + export const addOrReplaceCustomField = ( customFields: T[], customFieldToAdd: T @@ -25,3 +29,15 @@ export const addOrReplaceCustomField = ( return customFieldToAdd; }); }; + +export const customFieldSerializer = ( + field: CustomFieldConfiguration +): CustomFieldConfiguration => { + const { defaultValue, ...otherProperties } = field; + + if (defaultValue === undefined || (isString(defaultValue) && isEmptyString(defaultValue))) { + return otherProperties; + } + + return field; +}; diff --git a/x-pack/plugins/cases/public/components/files/file_type.test.tsx b/x-pack/plugins/cases/public/components/files/file_type.test.tsx index 6a96870f14cf9..d9c58fd6cab2e 100644 --- a/x-pack/plugins/cases/public/components/files/file_type.test.tsx +++ b/x-pack/plugins/cases/public/components/files/file_type.test.tsx @@ -17,7 +17,8 @@ import { basicCase, basicFileMock } from '../../containers/mock'; import { getFileType } from './file_type'; import { FILE_ATTACHMENT_TYPE } from '../../../common/constants'; -describe('getFileType', () => { +// Failing: See https://github.com/elastic/kibana/issues/175841 +describe.skip('getFileType', () => { const fileType = getFileType(); it('invalid props return blank FileAttachmentViewObject', () => { diff --git a/x-pack/plugins/cases/public/containers/mock.ts b/x-pack/plugins/cases/public/containers/mock.ts index ac3653f1d4a9f..63fac9c816955 100644 --- a/x-pack/plugins/cases/public/containers/mock.ts +++ b/x-pack/plugins/cases/public/containers/mock.ts @@ -1155,9 +1155,25 @@ export const getCaseUsersMockResponse = (): CaseUsers => { export const customFieldsMock: CaseUICustomField[] = [ { type: CustomFieldTypes.TEXT, key: 'test_key_1', value: 'My text test value 1' }, { type: CustomFieldTypes.TOGGLE, key: 'test_key_2', value: true }, + { type: CustomFieldTypes.TEXT, key: 'test_key_3', value: null }, + { type: CustomFieldTypes.TOGGLE, key: 'test_key_4', value: null }, ]; export const customFieldsConfigurationMock: CasesConfigurationUICustomField[] = [ - { type: CustomFieldTypes.TEXT, key: 'test_key_1', label: 'My test label 1', required: true }, - { type: CustomFieldTypes.TOGGLE, key: 'test_key_2', label: 'My test label 2', required: false }, + { + type: CustomFieldTypes.TEXT, + key: 'test_key_1', + label: 'My test label 1', + required: true, + defaultValue: 'My default value', + }, + { + type: CustomFieldTypes.TOGGLE, + key: 'test_key_2', + label: 'My test label 2', + required: true, + defaultValue: true, + }, + { type: CustomFieldTypes.TEXT, key: 'test_key_3', label: 'My test label 3', required: false }, + { type: CustomFieldTypes.TOGGLE, key: 'test_key_4', label: 'My test label 4', required: false }, ]; diff --git a/x-pack/plugins/cases/server/client/cases/bulk_create.test.ts b/x-pack/plugins/cases/server/client/cases/bulk_create.test.ts index fa0b4e3f584e5..394a95ed26d9c 100644 --- a/x-pack/plugins/cases/server/client/cases/bulk_create.test.ts +++ b/x-pack/plugins/cases/server/client/cases/bulk_create.test.ts @@ -752,29 +752,23 @@ describe('bulkCreate', () => { describe('Custom Fields', () => { const clientArgs = createCasesClientMockArgs(); - clientArgs.services.caseService.bulkCreateCases.mockResolvedValue({ saved_objects: [caseSO] }); const theCase = getCases()[0]; - const casesClient = createCasesClientMock(); - casesClient.configure.get = jest.fn().mockResolvedValue([ + const defaultCustomFieldsConfiguration = [ { - owner: theCase.owner, - customFields: [ - { - key: 'first_key', - type: CustomFieldTypes.TEXT, - label: 'foo', - required: true, - }, - { - key: 'second_key', - type: CustomFieldTypes.TOGGLE, - label: 'foo', - required: false, - }, - ], + key: 'first_key', + type: CustomFieldTypes.TEXT, + label: 'label 1', + required: true, + defaultValue: 'default value', }, - ]); + { + key: 'second_key', + type: CustomFieldTypes.TOGGLE, + label: 'label 2', + required: false, + }, + ]; const theCustomFields: CaseCustomFields = [ { @@ -789,6 +783,19 @@ describe('bulkCreate', () => { }, ]; + beforeEach(() => { + jest.clearAllMocks(); + clientArgs.services.caseService.bulkCreateCases.mockResolvedValue({ + saved_objects: [caseSO], + }); + casesClient.configure.get = jest.fn().mockResolvedValue([ + { + owner: theCase.owner, + customFields: defaultCustomFieldsConfiguration, + }, + ]); + }); + it('should bulkCreate customFields correctly', async () => { await expect( bulkCreate({ cases: getCases({ customFields: theCustomFields }) }, clientArgs, casesClient) @@ -800,22 +807,16 @@ describe('bulkCreate', () => { expect(customFields).toEqual(theCustomFields); }); - it('should not throw an error and fill out missing customFields when they are undefined', async () => { + it('fills out missing required custom fields', async () => { casesClient.configure.get = jest.fn().mockResolvedValue([ { owner: theCase.owner, customFields: [ + defaultCustomFieldsConfiguration[0], { - key: 'first_key', - type: CustomFieldTypes.TEXT, - label: 'foo', - required: false, - }, - { - key: 'second_key', - type: CustomFieldTypes.TOGGLE, - label: 'foo', - required: false, + ...defaultCustomFieldsConfiguration[1], + required: true, + defaultValue: true, }, ], }, @@ -829,55 +830,25 @@ describe('bulkCreate', () => { clientArgs.services.caseService.bulkCreateCases.mock.calls[0][0].cases[0].customFields; expect(customFields).toEqual([ - { key: 'first_key', type: 'text', value: null }, - { key: 'second_key', type: 'toggle', value: null }, - ]); - }); - - it('should throw an error when required customFields are undefined', async () => { - casesClient.configure.get = jest.fn().mockResolvedValue([ - { - owner: theCase.owner, - customFields: [ - { - key: 'first_key', - type: CustomFieldTypes.TEXT, - label: 'missing field 1', - required: true, - }, - { - key: 'second_key', - type: CustomFieldTypes.TOGGLE, - label: 'foo', - required: false, - }, - ], - }, + { key: 'first_key', type: 'text', value: 'default value' }, + { key: 'second_key', type: 'toggle', value: true }, ]); - - await expect( - bulkCreate({ cases: getCases() }, clientArgs, casesClient) - ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Failed to bulk create cases: Error: Missing required custom fields: \\"missing field 1\\""` - ); }); - it('should throw an error when required customFields are null', async () => { + it('throws error when required customFields are null', async () => { casesClient.configure.get = jest.fn().mockResolvedValue([ { owner: theCase.owner, customFields: [ { - key: 'first_key', - type: CustomFieldTypes.TEXT, + ...defaultCustomFieldsConfiguration[0], label: 'missing field 1', - required: true, }, { - key: 'second_key', - type: CustomFieldTypes.TOGGLE, + ...defaultCustomFieldsConfiguration[1], label: 'missing field 2', required: true, + defaultValue: true, }, ], }, @@ -905,7 +876,32 @@ describe('bulkCreate', () => { casesClient ) ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Failed to bulk create cases: Error: Missing required custom fields: \\"missing field 1\\", \\"missing field 2\\""` + `"Failed to bulk create cases: Error: Invalid value \\"null\\" supplied for the following required custom fields: \\"missing field 1\\", \\"missing field 2\\""` + ); + }); + + it('throws error when required customFields are undefined and missing a default value', async () => { + casesClient.configure.get = jest.fn().mockResolvedValue([ + { + owner: theCase.owner, + customFields: [ + { + ...defaultCustomFieldsConfiguration[0], + required: true, + defaultValue: undefined, + }, + { + ...defaultCustomFieldsConfiguration[1], + required: true, + }, + ], + }, + ]); + + await expect( + bulkCreate({ cases: getCases() }, clientArgs, casesClient) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Failed to bulk create cases: Error: Missing required custom fields without default value configured: \\"label 1\\", \\"label 2\\""` ); }); @@ -925,7 +921,7 @@ describe('bulkCreate', () => { ); }); - it('throws with duplicated customFields keys', async () => { + it('throws error with duplicated customFields keys', async () => { await expect( bulkCreate( { @@ -974,28 +970,6 @@ describe('bulkCreate', () => { ); }); - it('throws error when required custom fields are missing', async () => { - await expect( - bulkCreate( - { - cases: getCases({ - customFields: [ - { - key: 'second_key', - type: CustomFieldTypes.TEXT, - value: 'this is a text field value', - }, - ], - }), - }, - clientArgs, - casesClient - ) - ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Failed to bulk create cases: Error: Missing required custom fields: \\"missing field 1\\""` - ); - }); - it('throws when the customField types do not match the configuration', async () => { await expect( bulkCreate( @@ -1019,7 +993,7 @@ describe('bulkCreate', () => { casesClient ) ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Failed to bulk create cases: Error: The following custom fields have the wrong type in the request: first_key,second_key"` + `"Failed to bulk create cases: Error: The following custom fields have the wrong type in the request: \\"label 1\\", \\"label 2\\""` ); }); @@ -1062,7 +1036,7 @@ describe('bulkCreate', () => { await expect( bulkCreate({ cases: casesWithDifferentOwners }, clientArgs, casesClient) ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Failed to bulk create cases: Error: Missing required custom fields: \\"stack cases custom field\\""` + `"Failed to bulk create cases: Error: Missing required custom fields without default value configured: \\"stack cases custom field\\""` ); }); diff --git a/x-pack/plugins/cases/server/client/cases/create.test.ts b/x-pack/plugins/cases/server/client/cases/create.test.ts index b65061d403fec..315ee14834574 100644 --- a/x-pack/plugins/cases/server/client/cases/create.test.ts +++ b/x-pack/plugins/cases/server/client/cases/create.test.ts @@ -432,25 +432,21 @@ describe('create', () => { clientArgs.services.caseService.createCase.mockResolvedValue(caseSO); const casesClient = createCasesClientMock(); - casesClient.configure.get = jest.fn().mockResolvedValue([ + const defaultCustomFieldsConfiguration = [ { - owner: theCase.owner, - customFields: [ - { - key: 'first_key', - type: CustomFieldTypes.TEXT, - label: 'foo', - required: true, - }, - { - key: 'second_key', - type: CustomFieldTypes.TOGGLE, - label: 'foo', - required: false, - }, - ], + key: 'first_key', + type: CustomFieldTypes.TEXT, + label: 'label 1', + required: true, + defaultValue: 'default value', }, - ]); + { + key: 'second_key', + type: CustomFieldTypes.TOGGLE, + label: 'label 2', + required: false, + }, + ]; const theCustomFields: CaseCustomFields = [ { @@ -467,6 +463,12 @@ describe('create', () => { beforeEach(() => { jest.clearAllMocks(); + casesClient.configure.get = jest.fn().mockResolvedValue([ + { + owner: theCase.owner, + customFields: defaultCustomFieldsConfiguration, + }, + ]); }); it('should create customFields correctly', async () => { @@ -503,26 +505,7 @@ describe('create', () => { ); }); - it('should not throw an error and fill out missing customFields when they are undefined', async () => { - casesClient.configure.get = jest.fn().mockResolvedValue([ - { - owner: theCase.owner, - customFields: [ - { - key: 'first_key', - type: CustomFieldTypes.TEXT, - label: 'foo', - required: false, - }, - { - key: 'second_key', - type: CustomFieldTypes.TOGGLE, - label: 'foo', - required: false, - }, - ], - }, - ]); + it('fills out missing required custom fields', async () => { await expect(create({ ...theCase }, clientArgs, casesClient)).resolves.not.toThrow(); expect(clientArgs.services.caseService.createCase).toHaveBeenCalledWith( @@ -540,7 +523,7 @@ describe('create', () => { duration: null, status: CaseStatuses.open, customFields: [ - { key: 'first_key', type: 'text', value: null }, + { key: 'first_key', type: 'text', value: 'default value' }, { key: 'second_key', type: 'toggle', value: null }, ], }, @@ -550,48 +533,54 @@ describe('create', () => { ); }); - it('should throw an error when required customFields are undefined', async () => { + it('should throw an error when required customFields are null', async () => { casesClient.configure.get = jest.fn().mockResolvedValue([ { owner: theCase.owner, customFields: [ { - key: 'first_key', - type: CustomFieldTypes.TEXT, + ...defaultCustomFieldsConfiguration[0], label: 'missing field 1', - required: true, }, { - key: 'second_key', - type: CustomFieldTypes.TOGGLE, - label: 'foo', - required: false, + ...defaultCustomFieldsConfiguration[1], + label: 'missing field 2', + required: true, + defaultValue: false, }, ], }, ]); await expect( - create({ ...theCase }, clientArgs, casesClient) + create( + { + ...theCase, + customFields: [ + { ...theCustomFields[0], value: null }, + { ...theCustomFields[1], value: null }, + ], + }, + clientArgs, + casesClient + ) ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Failed to create case: Error: Missing required custom fields: \\"missing field 1\\""` + `"Failed to create case: Error: Invalid value \\"null\\" supplied for the following required custom fields: \\"missing field 1\\", \\"missing field 2\\""` ); }); - it('should throw an error when required customFields are null', async () => { + it('should throw an error when required customFields are undefined and missing a default value', async () => { casesClient.configure.get = jest.fn().mockResolvedValue([ { owner: theCase.owner, customFields: [ { - key: 'first_key', - type: CustomFieldTypes.TEXT, + ...defaultCustomFieldsConfiguration[0], label: 'missing field 1', - required: true, + defaultValue: undefined, }, { - key: 'second_key', - type: CustomFieldTypes.TOGGLE, + ...defaultCustomFieldsConfiguration[1], label: 'missing field 2', required: true, }, @@ -600,27 +589,9 @@ describe('create', () => { ]); await expect( - create( - { - ...theCase, - customFields: [ - { - key: 'first_key', - type: CustomFieldTypes.TEXT, - value: null, - }, - { - key: 'second_key', - type: CustomFieldTypes.TOGGLE, - value: null, - }, - ], - }, - clientArgs, - casesClient - ) + create({ ...theCase }, clientArgs, casesClient) ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Failed to create case: Error: Missing required custom fields: \\"missing field 1\\", \\"missing field 2\\""` + `"Failed to create case: Error: Missing required custom fields without default value configured: \\"missing field 1\\", \\"missing field 2\\""` ); }); @@ -686,27 +657,6 @@ describe('create', () => { ); }); - it('throws error when required custom fields are missing', async () => { - await expect( - create( - { - ...theCase, - customFields: [ - { - key: 'second_key', - type: CustomFieldTypes.TEXT, - value: 'this is a text field value', - }, - ], - }, - clientArgs, - casesClient - ) - ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Failed to create case: Error: Missing required custom fields: \\"missing field 1\\""` - ); - }); - it('throws when the customField types do not match the configuration', async () => { await expect( create( @@ -729,7 +679,7 @@ describe('create', () => { casesClient ) ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Failed to create case: Error: The following custom fields have the wrong type in the request: first_key,second_key"` + `"Failed to create case: Error: The following custom fields have the wrong type in the request: \\"label 1\\", \\"label 2\\""` ); }); }); diff --git a/x-pack/plugins/cases/server/client/cases/update.test.ts b/x-pack/plugins/cases/server/client/cases/update.test.ts index 29bce4636c3f3..c962b21be36d1 100644 --- a/x-pack/plugins/cases/server/client/cases/update.test.ts +++ b/x-pack/plugins/cases/server/client/cases/update.test.ts @@ -1052,6 +1052,21 @@ describe('update', () => { describe('Custom Fields', () => { const clientArgs = createCasesClientMockArgs(); const casesClient = createCasesClientMock(); + const defaultCustomFieldsConfiguration = [ + { + key: 'first_key', + type: CustomFieldTypes.TEXT, + label: 'missing field 1', + required: true, + defaultValue: 'default value', + }, + { + key: 'second_key', + type: CustomFieldTypes.TOGGLE, + label: 'foo', + required: false, + }, + ]; beforeEach(() => { jest.clearAllMocks(); @@ -1066,20 +1081,7 @@ describe('update', () => { casesClient.configure.get = jest.fn().mockResolvedValue([ { owner: mockCases[0].attributes.owner, - customFields: [ - { - key: 'first_key', - type: CustomFieldTypes.TEXT, - label: 'missing field 1', - required: true, - }, - { - key: 'second_key', - type: CustomFieldTypes.TOGGLE, - label: 'foo', - required: false, - }, - ], + customFields: defaultCustomFieldsConfiguration, }, ]); clientArgs.services.attachmentService.getter.getCaseCommentStats.mockResolvedValue(new Map()); @@ -1197,6 +1199,63 @@ describe('update', () => { ); }); + it('fills out missing required custom fields', async () => { + const customFields = [ + { + key: 'second_key', + type: CustomFieldTypes.TOGGLE as const, + value: false, + }, + ]; + + clientArgs.services.caseService.patchCases.mockResolvedValue({ + saved_objects: [{ ...mockCases[0] }], + }); + + await expect( + update( + { + cases: [ + { + id: mockCases[0].id, + version: mockCases[0].version ?? '', + customFields, + }, + ], + }, + clientArgs, + casesClient + ) + ).resolves.not.toThrow(); + + expect(clientArgs.services.caseService.patchCases).toHaveBeenCalledWith( + expect.objectContaining({ + cases: [ + { + caseId: mockCases[0].id, + version: mockCases[0].version, + originalCase: { + ...mockCases[0], + }, + updatedAttributes: { + customFields: [ + ...customFields, + { + key: 'first_key', + type: CustomFieldTypes.TEXT as const, + value: 'default value', + }, + ], + updated_at: expect.any(String), + updated_by: expect.any(Object), + }, + }, + ], + refresh: false, + }) + ); + }); + it('throws error when the customFields array is too long', async () => { const customFields = Array(MAX_CUSTOM_FIELDS_PER_CASE + 1).fill({ key: 'first_custom_field_key', @@ -1285,7 +1344,29 @@ describe('update', () => { ); }); - it('throws error when custom fields are missing', async () => { + it('throws error when required custom fields are null', async () => { + casesClient.configure.get = jest.fn().mockResolvedValue([ + { + owner: mockCases[0].attributes.owner, + customFields: [ + { + key: 'first_key', + type: CustomFieldTypes.TEXT, + label: 'missing field 1', + required: true, + defaultValue: 'default value', + }, + { + key: 'second_key', + type: CustomFieldTypes.TOGGLE, + label: 'missing field 2', + required: true, + defaultValue: true, + }, + ], + }, + ]); + await expect( update( { @@ -1294,6 +1375,11 @@ describe('update', () => { id: mockCases[0].id, version: mockCases[0].version ?? '', customFields: [ + { + key: 'first_key', + type: CustomFieldTypes.TEXT, + value: null, + }, { key: 'second_key', type: CustomFieldTypes.TOGGLE, @@ -1307,7 +1393,47 @@ describe('update', () => { casesClient ) ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Failed to update case, ids: [{\\"id\\":\\"mock-id-1\\",\\"version\\":\\"WzAsMV0=\\"}]: Error: Missing required custom fields: \\"missing field 1\\""` + `"Failed to update case, ids: [{\\"id\\":\\"mock-id-1\\",\\"version\\":\\"WzAsMV0=\\"}]: Error: Invalid value \\"null\\" supplied for the following required custom fields: \\"missing field 1\\", \\"missing field 2\\""` + ); + }); + + it('throws error when required custom fields are undefined and missing a default value', async () => { + casesClient.configure.get = jest.fn().mockResolvedValue([ + { + owner: mockCases[0].attributes.owner, + customFields: [ + { + key: 'first_key', + type: CustomFieldTypes.TEXT, + label: 'missing field 1', + required: true, + }, + { + key: 'second_key', + type: CustomFieldTypes.TOGGLE, + label: 'missing field 2', + required: true, + }, + ], + }, + ]); + + await expect( + update( + { + cases: [ + { + id: mockCases[0].id, + version: mockCases[0].version ?? '', + customFields: [], + }, + ], + }, + clientArgs, + casesClient + ) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Failed to update case, ids: [{\\"id\\":\\"mock-id-1\\",\\"version\\":\\"WzAsMV0=\\"}]: Error: All update fields are identical to current version."` ); }); @@ -1338,7 +1464,7 @@ describe('update', () => { casesClient ) ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Failed to update case, ids: [{\\"id\\":\\"mock-id-1\\",\\"version\\":\\"WzAsMV0=\\"}]: Error: The following custom fields have the wrong type in the request: first_key,second_key"` + `"Failed to update case, ids: [{\\"id\\":\\"mock-id-1\\",\\"version\\":\\"WzAsMV0=\\"}]: Error: The following custom fields have the wrong type in the request: \\"missing field 1\\", \\"foo\\""` ); }); }); diff --git a/x-pack/plugins/cases/server/client/cases/utils.test.ts b/x-pack/plugins/cases/server/client/cases/utils.test.ts index 2d697b597baac..a1c070eb08d1f 100644 --- a/x-pack/plugins/cases/server/client/cases/utils.test.ts +++ b/x-pack/plugins/cases/server/client/cases/utils.test.ts @@ -32,7 +32,7 @@ import { fillMissingCustomFields, normalizeCreateCaseRequest, } from './utils'; -import type { CaseCustomFields } from '../../../common/types/domain'; +import type { CaseCustomFields, CustomFieldsConfiguration } from '../../../common/types/domain'; import { CaseStatuses, CustomFieldTypes, @@ -1363,9 +1363,24 @@ describe('utils', () => { type: CustomFieldTypes.TEXT, value: 'this is a text field value', }, + { + key: 'second_key', + type: CustomFieldTypes.TOGGLE, + value: null, + }, + { + key: 'third_key', + type: CustomFieldTypes.TEXT, + value: 'default value', + }, + { + key: 'fourth_key', + type: CustomFieldTypes.TOGGLE, + value: false, + }, ]; - const customFieldsConfiguration = [ + const customFieldsConfiguration: CustomFieldsConfiguration = [ { key: 'first_key', type: CustomFieldTypes.TEXT, @@ -1378,59 +1393,117 @@ describe('utils', () => { label: 'foo', required: false, }, + { + key: 'third_key', + type: CustomFieldTypes.TEXT, + label: 'foo', + required: true, + defaultValue: 'default value', + }, + { + key: 'fourth_key', + type: CustomFieldTypes.TOGGLE, + label: 'foo', + required: true, + defaultValue: false, + }, ]; it('adds missing custom fields correctly', () => { expect( fillMissingCustomFields({ - customFields, + customFields: [customFields[0]], customFieldsConfiguration, }) + ).toEqual(customFields); + }); + + it('does not use default value for optional custom fields', () => { + expect( + fillMissingCustomFields({ + customFields: [], + customFieldsConfiguration: [ + // @ts-ignore: expected + { ...customFieldsConfiguration[0], defaultValue: 'default value' }, + // @ts-ignore: expected + { ...customFieldsConfiguration[1], defaultValue: true }, + ], + }) ).toEqual([ - customFields[0], - { - key: 'second_key', - type: CustomFieldTypes.TOGGLE, - value: null, - }, + { ...customFields[0], value: null }, + { ...customFields[1], value: null }, ]); }); - it('does not set to null custom fields that exists', () => { + it('does not set to null custom fields that exist', () => { expect( fillMissingCustomFields({ - customFields: [ - customFields[0], - { - key: 'second_key', - type: CustomFieldTypes.TOGGLE, - value: true, - }, - ], + customFields: [customFields[0], customFields[1]], customFieldsConfiguration, }) - ).toEqual([ + ).toEqual(customFields); + }); + + it('does not update existing required custom fields to their default value', () => { + const customFieldsToTest = [ customFields[0], - { - key: 'second_key', - type: CustomFieldTypes.TOGGLE, - value: true, - }, - ]); + customFields[1], + { ...customFields[2], value: 'not the default' }, + { ...customFields[3], value: true }, + ] as CaseCustomFields; + + expect( + fillMissingCustomFields({ + customFields: customFieldsToTest, + customFieldsConfiguration, + }) + ).toEqual(customFieldsToTest); + }); + + it('does not insert missing required custom fields if default value is null', () => { + const customFieldsToTest = [customFields[0], customFields[1]] as CaseCustomFields; + + expect( + fillMissingCustomFields({ + customFields: customFieldsToTest, + customFieldsConfiguration: [ + customFieldsConfiguration[0], + customFieldsConfiguration[1], + { ...customFieldsConfiguration[2], defaultValue: null }, + { ...customFieldsConfiguration[3], defaultValue: null }, + ], + }) + ).toEqual(customFieldsToTest); + }); + + it('does not insert missing required custom fields if default value is undefined', () => { + const customFieldsToTest = [customFields[0], customFields[1]] as CaseCustomFields; + + expect( + fillMissingCustomFields({ + customFields: customFieldsToTest, + customFieldsConfiguration: [ + customFieldsConfiguration[0], + customFieldsConfiguration[1], + { ...customFieldsConfiguration[2], defaultValue: undefined }, + { ...customFieldsConfiguration[3], defaultValue: undefined }, + ], + }) + ).toEqual(customFieldsToTest); }); it('returns all custom fields if they are more than the configuration', () => { expect( fillMissingCustomFields({ customFields: [ - customFields[0], + ...customFields, { - key: 'second_key', + key: 'extra 1', type: CustomFieldTypes.TOGGLE, value: true, }, { - key: 'third_key', + key: 'extra 2', type: CustomFieldTypes.TOGGLE, value: true, }, @@ -1438,21 +1511,21 @@ describe('utils', () => { customFieldsConfiguration, }) ).toEqual([ - customFields[0], + ...customFields, { - key: 'second_key', + key: 'extra 1', type: CustomFieldTypes.TOGGLE, value: true, }, { - key: 'third_key', + key: 'extra 2', type: CustomFieldTypes.TOGGLE, value: true, }, ]); }); - it('adds missing custom fields if the customFields is undefined', () => { + it('adds missing custom fields if they are undefined', () => { expect( fillMissingCustomFields({ customFieldsConfiguration, @@ -1468,6 +1541,8 @@ describe('utils', () => { type: CustomFieldTypes.TOGGLE, value: null, }, + customFields[2], + customFields[3], ]); }); diff --git a/x-pack/plugins/cases/server/client/cases/utils.ts b/x-pack/plugins/cases/server/client/cases/utils.ts index 474871af02c5d..b304159b928aa 100644 --- a/x-pack/plugins/cases/server/client/cases/utils.ts +++ b/x-pack/plugins/cases/server/client/cases/utils.ts @@ -16,6 +16,7 @@ import type { Case, CaseAssignees, CaseAttributes, + CaseCustomField, ConnectorMappings, ConnectorMappingSource, ConnectorMappingTarget, @@ -469,13 +470,26 @@ export const fillMissingCustomFields = ({ const customFieldsKeys = new Set(customFields.map((customField) => customField.key)); const missingCustomFields: CaseRequestCustomFields = []; + // only populate with the default value required custom fields missing from the request for (const confCustomField of customFieldsConfiguration) { if (!customFieldsKeys.has(confCustomField.key)) { - missingCustomFields.push({ - key: confCustomField.key, - type: confCustomField.type, - value: null, - }); + if ( + confCustomField.required && + confCustomField?.defaultValue !== null && + confCustomField?.defaultValue !== undefined + ) { + missingCustomFields.push({ + key: confCustomField.key, + type: confCustomField.type, + value: confCustomField.defaultValue, + } as CaseCustomField); + } else if (!confCustomField.required) { + missingCustomFields.push({ + key: confCustomField.key, + type: confCustomField.type, + value: null, + } as CaseCustomField); + } // else, missing required custom fields without default are not touched } } diff --git a/x-pack/plugins/cases/server/client/cases/validators.test.ts b/x-pack/plugins/cases/server/client/cases/validators.test.ts index abc4c27385b02..3e23a9993e934 100644 --- a/x-pack/plugins/cases/server/client/cases/validators.test.ts +++ b/x-pack/plugins/cases/server/client/cases/validators.test.ts @@ -100,7 +100,7 @@ describe('validators', () => { { key: 'first_key', type: CustomFieldTypes.TEXT, - label: 'foo', + label: 'first label', required: false, }, { @@ -112,7 +112,7 @@ describe('validators', () => { ] as CustomFieldsConfiguration, }) ).toThrowErrorMatchingInlineSnapshot( - `"The following custom fields have the wrong type in the request: first_key"` + `"The following custom fields have the wrong type in the request: \\"first label\\""` ); }); @@ -141,25 +141,59 @@ describe('validators', () => { { key: 'first_key', type: CustomFieldTypes.TEXT, - label: 'foo', + label: 'first label', required: false, }, { key: 'second_key', type: CustomFieldTypes.TEXT, - label: 'foo', + label: 'second label', required: false, }, { key: 'third_key', type: CustomFieldTypes.TOGGLE, - label: 'foo', + label: 'third label', required: false, }, ] as CustomFieldsConfiguration, }) ).toThrowErrorMatchingInlineSnapshot( - `"The following custom fields have the wrong type in the request: first_key,second_key,third_key"` + `"The following custom fields have the wrong type in the request: \\"first label\\", \\"second label\\", \\"third label\\""` + ); + }); + + it('throws with label unknown for missing custom field labels', () => { + expect(() => + validateCustomFieldTypesInRequest({ + requestCustomFields: [ + { + key: 'first_key', + type: CustomFieldTypes.TOGGLE, + value: true, + }, + { + key: 'second_key', + type: CustomFieldTypes.TEXT, + value: 'foobar', + }, + ], + + customFieldsConfiguration: [ + { + key: 'first_key', + type: CustomFieldTypes.TEXT, + required: false, + }, + { + key: 'second_key', + type: CustomFieldTypes.TEXT, + required: false, + }, + ] as CustomFieldsConfiguration, + }) + ).toThrowErrorMatchingInlineSnapshot( + `"The following custom fields have the wrong type in the request: \\"Unknown\\""` ); }); @@ -319,6 +353,32 @@ describe('validators', () => { ).not.toThrow(); }); + it('does not throw if all missing required custom fields have default values', () => { + const customFieldsConfiguration: CustomFieldsConfiguration = [ + { + key: 'first_key', + type: CustomFieldTypes.TEXT, + label: 'foo', + required: true, + defaultValue: 'default value', + }, + { + key: 'second_key', + type: CustomFieldTypes.TOGGLE, + label: 'foo', + required: true, + defaultValue: false, + }, + ]; + + expect(() => + validateRequiredCustomFields({ + requestCustomFields: [], + customFieldsConfiguration, + }) + ).not.toThrow(); + }); + it('does not throw if there are only optional custom fields in configuration', () => { const customFieldsConfiguration: CustomFieldsConfiguration = [ { @@ -346,7 +406,7 @@ describe('validators', () => { expect(() => validateRequiredCustomFields({})).not.toThrow(); }); - it('throws if there are missing required custom fields', () => { + it('throws if there are missing required custom fields without a default value', () => { const requestCustomFields: CaseCustomFields = [ { key: 'second_key', @@ -366,6 +426,14 @@ describe('validators', () => { type: CustomFieldTypes.TOGGLE, label: 'foo', required: true, + defaultValue: null, + }, + { + key: 'third_key', + type: CustomFieldTypes.TEXT, + label: 'foo', + required: true, + defaultValue: 'default value', }, ]; expect(() => @@ -374,11 +442,11 @@ describe('validators', () => { customFieldsConfiguration, }) ).toThrowErrorMatchingInlineSnapshot( - `"Missing required custom fields: \\"missing field 1\\""` + `"Missing required custom fields without default value configured: \\"missing field 1\\""` ); }); - it('throws if required custom fields have null value', () => { + it('throws if required custom fields with default have null value', () => { const requestCustomFields: CaseCustomFields = [ { key: 'second_key', @@ -388,11 +456,32 @@ describe('validators', () => { ]; const customFieldsConfiguration: CustomFieldsConfiguration = [ { - key: 'first_key', - type: CustomFieldTypes.TEXT, - label: 'missing field 1', + key: 'second_key', + type: CustomFieldTypes.TOGGLE, + label: 'missing field 2', required: true, + defaultValue: true, }, + ]; + expect(() => + validateRequiredCustomFields({ + requestCustomFields, + customFieldsConfiguration, + }) + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid value \\"null\\" supplied for the following required custom fields: \\"missing field 2\\""` + ); + }); + + it('throws if required custom fields without default have null value', () => { + const requestCustomFields: CaseCustomFields = [ + { + key: 'second_key', + type: CustomFieldTypes.TOGGLE, + value: null, + }, + ]; + const customFieldsConfiguration: CustomFieldsConfiguration = [ { key: 'second_key', type: CustomFieldTypes.TOGGLE, @@ -406,7 +495,7 @@ describe('validators', () => { customFieldsConfiguration, }) ).toThrowErrorMatchingInlineSnapshot( - `"Missing required custom fields: \\"missing field 1\\", \\"missing field 2\\""` + `"Invalid value \\"null\\" supplied for the following required custom fields: \\"missing field 2\\""` ); }); @@ -425,13 +514,45 @@ describe('validators', () => { ).toThrowErrorMatchingInlineSnapshot(`"No custom fields configured."`); }); - it('throws if configuration has required fields but request has no custom fields', () => { + it('throws if all missing required custom fields do not have default values', () => { + const customFieldsConfiguration: CustomFieldsConfiguration = [ + { + key: 'first_key', + type: CustomFieldTypes.TEXT, + label: 'missing field 1', + required: true, + defaultValue: null, + }, + { + key: 'second_key', + type: CustomFieldTypes.TOGGLE, + label: 'foo', + required: true, + }, + ]; + expect(() => + validateRequiredCustomFields({ + customFieldsConfiguration, + }) + ).toThrowErrorMatchingInlineSnapshot( + `"Missing required custom fields without default value configured: \\"missing field 1\\", \\"foo\\""` + ); + }); + + it('throws if some missing required custom fields do not have default values', () => { const customFieldsConfiguration: CustomFieldsConfiguration = [ { key: 'first_key', type: CustomFieldTypes.TEXT, label: 'missing field 1', required: true, + defaultValue: 'default value', + }, + { + key: 'second_key', + type: CustomFieldTypes.TOGGLE, + label: 'foo', + required: true, }, ]; expect(() => @@ -439,7 +560,7 @@ describe('validators', () => { customFieldsConfiguration, }) ).toThrowErrorMatchingInlineSnapshot( - `"Missing required custom fields: \\"missing field 1\\""` + `"Missing required custom fields without default value configured: \\"foo\\""` ); }); }); @@ -568,9 +689,7 @@ describe('validators', () => { customFieldsConfiguration, customFields: customFieldsMax, }) - ).toThrowErrorMatchingInlineSnapshot( - `"Maximum ${MAX_CUSTOM_FIELDS_PER_CASE} customFields are allowed."` - ); + ).toThrowErrorMatchingInlineSnapshot(`"Maximum 10 customFields are allowed."`); }); }); }); diff --git a/x-pack/plugins/cases/server/client/cases/validators.ts b/x-pack/plugins/cases/server/client/cases/validators.ts index 0d76875be0114..eeebbc8c13ca0 100644 --- a/x-pack/plugins/cases/server/client/cases/validators.ts +++ b/x-pack/plugins/cases/server/client/cases/validators.ts @@ -41,26 +41,18 @@ export function validateCustomFieldTypesInRequest({ throw Boom.badRequest('No custom fields configured.'); } - let invalidCustomFieldKeys: string[] = []; - - const validCustomFields = intersectionWith( + const invalidCustomFields = intersectionWith( customFieldsConfiguration, requestCustomFields, (requiredVal, requestedVal) => - requiredVal.key === requestedVal.key && requiredVal.type === requestedVal.type - ); - - if (requestCustomFields.length !== validCustomFields.length) { - invalidCustomFieldKeys = differenceWith( - requestCustomFields, - validCustomFields, - (requiredVal, requestedVal) => requiredVal.key === requestedVal.key - ).map((e) => e.key); - } + requiredVal.key === requestedVal.key && requiredVal.type !== requestedVal.type + ).map((config) => `"${config.label ? config.label : 'Unknown'}"`); - if (invalidCustomFieldKeys.length) { + if (invalidCustomFields.length) { throw Boom.badRequest( - `The following custom fields have the wrong type in the request: ${invalidCustomFieldKeys}` + `The following custom fields have the wrong type in the request: ${invalidCustomFields.join( + ', ' + )}` ); } } @@ -93,6 +85,7 @@ export const validateCustomFieldKeysAgainstConfiguration = ({ /** * Returns a list of required custom fields missing from the request + * that don't have a default value configured. */ export const validateRequiredCustomFields = ({ requestCustomFields, @@ -117,22 +110,37 @@ export const validateRequiredCustomFields = ({ const missingRequiredCustomFields = differenceWith( requiredCustomFields, requestCustomFields ?? [], - (requiredVal, requestedVal) => requiredVal.key === requestedVal.key - ).map((e) => `"${e.label}"`); + (configuration, request) => configuration.key === request.key + ) // missing custom field and missing defaultValue -> error + .filter( + (customField) => customField.defaultValue === undefined || customField.defaultValue === null + ) + .map((e) => `"${e.label}"`); - requiredCustomFields.forEach((requiredField) => { - const found = requestCustomFields?.find( - (requestField) => requestField.key === requiredField.key + if (missingRequiredCustomFields.length) { + throw Boom.badRequest( + `Missing required custom fields without default value configured: ${missingRequiredCustomFields.join( + ', ' + )}` ); + } - if (found && found.value === null) { - missingRequiredCustomFields.push(`"${requiredField.label}"`); - } - }); + const nullRequiredCustomFields = requiredCustomFields + .filter((requiredField) => { + const found = requestCustomFields?.find( + (requestField) => requestField.key === requiredField.key + ); - if (missingRequiredCustomFields.length) { + // required custom fields cannot be set to null + return found && found.value === null; + }) + .map((e) => `"${e.label}"`); + + if (nullRequiredCustomFields.length) { throw Boom.badRequest( - `Missing required custom fields: ${missingRequiredCustomFields.join(', ')}` + `Invalid value "null" supplied for the following required custom fields: ${nullRequiredCustomFields.join( + ', ' + )}` ); } }; diff --git a/x-pack/plugins/cases/server/client/configure/client.test.ts b/x-pack/plugins/cases/server/client/configure/client.test.ts index c92b1f96fbc3a..2e3c2a6899f91 100644 --- a/x-pack/plugins/cases/server/client/configure/client.test.ts +++ b/x-pack/plugins/cases/server/client/configure/client.test.ts @@ -333,9 +333,33 @@ describe('client', () => { customFields: [ { key: 'wrong_type_key', - label: 'text', + label: 'text label', + type: CustomFieldTypes.TEXT, + required: false, + }, + ], + }, + clientArgs, + casesClientInternal + ) + ).rejects.toThrow( + 'Failed to get patch configure in route: Error: Invalid custom field types in request for the following labels: "text label"' + ); + }); + + it('throws when an optional custom field has a default value', async () => { + await expect( + update( + 'test-id', + { + version: 'test-version', + customFields: [ + { + key: 'extra_default', + label: 'text label', type: CustomFieldTypes.TEXT, required: false, + defaultValue: 'foobar', }, ], }, @@ -343,7 +367,7 @@ describe('client', () => { casesClientInternal ) ).rejects.toThrow( - 'Failed to get patch configure in route: Error: Invalid custom field types in request for the following keys: wrong_type_key' + 'Failed to get patch configure in route: Error: The following optional custom fields try to define a default value: "text label"' ); }); }); @@ -407,5 +431,28 @@ describe('client', () => { 'Failed to create case configuration: Error: Invalid duplicated custom field keys in request: duplicated_key' ); }); + + it('throws when an optional custom field has a default value', async () => { + await expect( + create( + { + ...baseRequest, + customFields: [ + { + key: 'extra_default', + label: 'text label', + type: CustomFieldTypes.TEXT, + required: false, + defaultValue: 'foobar', + }, + ], + }, + clientArgs, + casesClientInternal + ) + ).rejects.toThrow( + 'Failed to create case configuration: Error: The following optional custom fields try to define a default value: "text label"' + ); + }); }); }); diff --git a/x-pack/plugins/cases/server/client/configure/client.ts b/x-pack/plugins/cases/server/client/configure/client.ts index 1261f1061a371..41a9dd9326c24 100644 --- a/x-pack/plugins/cases/server/client/configure/client.ts +++ b/x-pack/plugins/cases/server/client/configure/client.ts @@ -49,7 +49,10 @@ import { updateMappings } from './update_mappings'; import { decodeOrThrow } from '../../../common/api/runtime_types'; import { ConfigurationRt, ConfigurationsRt } from '../../../common/types/domain'; import { validateDuplicatedCustomFieldKeysInRequest } from '../validators'; -import { validateCustomFieldTypesInRequest } from './validators'; +import { + validateCustomFieldTypesInRequest, + validateOptionalCustomFieldsInRequest, +} from './validators'; /** * Defines the internal helper functions. @@ -253,6 +256,7 @@ export async function update( const request = decodeWithExcessOrThrow(ConfigurationPatchRequestRt)(req); validateDuplicatedCustomFieldKeysInRequest({ requestCustomFields: request.customFields }); + validateOptionalCustomFieldsInRequest({ requestCustomFields: request.customFields }); const { version, ...queryWithoutVersion } = request; @@ -368,6 +372,9 @@ export async function create( validateDuplicatedCustomFieldKeysInRequest({ requestCustomFields: validatedConfigurationRequest.customFields, }); + validateOptionalCustomFieldsInRequest({ + requestCustomFields: validatedConfigurationRequest.customFields, + }); let error = null; diff --git a/x-pack/plugins/cases/server/client/configure/validators.test.ts b/x-pack/plugins/cases/server/client/configure/validators.test.ts index 3ef853f0d671d..d1d41bfe1bb62 100644 --- a/x-pack/plugins/cases/server/client/configure/validators.test.ts +++ b/x-pack/plugins/cases/server/client/configure/validators.test.ts @@ -6,7 +6,10 @@ */ import { CustomFieldTypes } from '../../../common/types/domain'; -import { validateCustomFieldTypesInRequest } from './validators'; +import { + validateCustomFieldTypesInRequest, + validateOptionalCustomFieldsInRequest, +} from './validators'; describe('validators', () => { describe('validateCustomFieldTypesInRequest', () => { @@ -14,16 +17,17 @@ describe('validators', () => { expect(() => validateCustomFieldTypesInRequest({ requestCustomFields: [ - { key: '1', type: CustomFieldTypes.TOGGLE }, - { key: '2', type: CustomFieldTypes.TEXT }, + { key: '1', type: CustomFieldTypes.TOGGLE, label: 'label 1' }, + { key: '2', type: CustomFieldTypes.TEXT, label: 'label 2' }, ], + originalCustomFields: [ { key: '1', type: CustomFieldTypes.TEXT }, { key: '2', type: CustomFieldTypes.TOGGLE }, ], }) ).toThrowErrorMatchingInlineSnapshot( - `"Invalid custom field types in request for the following keys: 1,2"` + `"Invalid custom field types in request for the following labels: \\"label 1\\", \\"label 2\\""` ); }); @@ -31,16 +35,17 @@ describe('validators', () => { expect(() => validateCustomFieldTypesInRequest({ requestCustomFields: [ - { key: '1', type: CustomFieldTypes.TOGGLE }, - { key: '2', type: CustomFieldTypes.TOGGLE }, + { key: '1', type: CustomFieldTypes.TOGGLE, label: 'label 1' }, + { key: '2', type: CustomFieldTypes.TOGGLE, label: 'label 2' }, ], + originalCustomFields: [ { key: '1', type: CustomFieldTypes.TEXT }, { key: '2', type: CustomFieldTypes.TOGGLE }, ], }) ).toThrowErrorMatchingInlineSnapshot( - `"Invalid custom field types in request for the following keys: 1"` + `"Invalid custom field types in request for the following labels: \\"label 1\\""` ); }); @@ -59,12 +64,81 @@ describe('validators', () => { expect(() => validateCustomFieldTypesInRequest({ requestCustomFields: [ - { key: '1', type: CustomFieldTypes.TOGGLE }, - { key: '2', type: CustomFieldTypes.TEXT }, + { key: '1', type: CustomFieldTypes.TOGGLE, label: 'label 1' }, + { key: '2', type: CustomFieldTypes.TEXT, label: 'label 2' }, ], originalCustomFields: [], }) ).not.toThrow(); }); }); + + describe('validateOptionalCustomFieldsInRequest', () => { + it('does not throw an error for properly constructed optional custom fields', () => { + expect(() => + validateOptionalCustomFieldsInRequest({ + requestCustomFields: [ + { key: '1', required: false, label: 'label 1' }, + { key: '2', required: false, label: 'label 2' }, + ], + }) + ).not.toThrow(); + }); + + it('does not throw an error for required custom fields with default values', () => { + expect(() => + validateOptionalCustomFieldsInRequest({ + requestCustomFields: [ + { key: '1', required: true, defaultValue: false, label: 'label 1' }, + { key: '2', required: true, defaultValue: 'foobar', label: 'label 2' }, + ], + }) + ).not.toThrow(); + }); + + it('throws an error even if the default value has the correct type', () => { + expect(() => + validateOptionalCustomFieldsInRequest({ + requestCustomFields: [ + { key: '1', required: false, defaultValue: false, label: 'label 1' }, + { key: '2', required: false, defaultValue: 'foobar', label: 'label 2' }, + ], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"The following optional custom fields try to define a default value: \\"label 1\\", \\"label 2\\""` + ); + }); + + it('throws an error for other falsy defaultValues (null)', () => { + expect(() => + validateOptionalCustomFieldsInRequest({ + requestCustomFields: [ + { key: '1', required: false, defaultValue: null, label: 'label 1' }, + ], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"The following optional custom fields try to define a default value: \\"label 1\\""` + ); + }); + + it('throws an error for other falsy defaultValues (0)', () => { + expect(() => + validateOptionalCustomFieldsInRequest({ + requestCustomFields: [{ key: '1', required: false, defaultValue: 0, label: 'label 1' }], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"The following optional custom fields try to define a default value: \\"label 1\\""` + ); + }); + + it('throws an error for other falsy defaultValues (empty string)', () => { + expect(() => + validateOptionalCustomFieldsInRequest({ + requestCustomFields: [{ key: '1', required: false, defaultValue: '', label: 'label 1' }], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"The following optional custom fields try to define a default value: \\"label 1\\""` + ); + }); + }); }); diff --git a/x-pack/plugins/cases/server/client/configure/validators.ts b/x-pack/plugins/cases/server/client/configure/validators.ts index 36743d1720376..ca3a175e40579 100644 --- a/x-pack/plugins/cases/server/client/configure/validators.ts +++ b/x-pack/plugins/cases/server/client/configure/validators.ts @@ -15,7 +15,7 @@ export const validateCustomFieldTypesInRequest = ({ requestCustomFields, originalCustomFields, }: { - requestCustomFields?: Array<{ key: string; type: CustomFieldTypes }>; + requestCustomFields?: Array<{ key: string; type: CustomFieldTypes; label: string }>; originalCustomFields: Array<{ key: string; type: CustomFieldTypes }>; }) => { if (!Array.isArray(requestCustomFields) || !originalCustomFields.length) { @@ -28,13 +28,47 @@ export const validateCustomFieldTypesInRequest = ({ const originalField = originalCustomFields.find((item) => item.key === requestField.key); if (originalField && originalField.type !== requestField.type) { - invalidFields.push(requestField.key); + invalidFields.push(`"${requestField.label}"`); } }); if (invalidFields.length > 0) { throw Boom.badRequest( - `Invalid custom field types in request for the following keys: ${invalidFields}` + `Invalid custom field types in request for the following labels: ${invalidFields.join(', ')}` + ); + } +}; + +/** + * Throws an error if any optional custom field defines a default value. + */ +export const validateOptionalCustomFieldsInRequest = ({ + requestCustomFields, +}: { + requestCustomFields?: Array<{ + key: string; + required: boolean; + defaultValue?: unknown; + label: string; + }>; +}) => { + if (!Array.isArray(requestCustomFields)) { + return; + } + + const invalidFields: string[] = []; + + requestCustomFields.forEach((requestField) => { + if (!requestField.required && requestField.defaultValue !== undefined) { + invalidFields.push(`"${requestField.label}"`); + } + }); + + if (invalidFields.length > 0) { + throw Boom.badRequest( + `The following optional custom fields try to define a default value: ${invalidFields.join( + ', ' + )}` ); } }; diff --git a/x-pack/plugins/cases/server/common/types/configure.ts b/x-pack/plugins/cases/server/common/types/configure.ts index a591375a4d439..94dcaf0a9ce19 100644 --- a/x-pack/plugins/cases/server/common/types/configure.ts +++ b/x-pack/plugins/cases/server/common/types/configure.ts @@ -33,6 +33,7 @@ type PersistedCustomFieldsConfiguration = Array<{ type: string; label: string; required: boolean; + defaultValue?: string | boolean | null; }>; export type ConfigurationTransformedAttributes = ConfigurationAttributes; diff --git a/x-pack/plugins/cases/server/services/configure/index.test.ts b/x-pack/plugins/cases/server/services/configure/index.test.ts index 3be7e771c5e64..d1a79cc1a8d6e 100644 --- a/x-pack/plugins/cases/server/services/configure/index.test.ts +++ b/x-pack/plugins/cases/server/services/configure/index.test.ts @@ -6,7 +6,7 @@ */ import type { CaseConnector, ConfigurationAttributes } from '../../../common/types/domain'; -import { ConnectorTypes } from '../../../common/types/domain'; +import { CustomFieldTypes, ConnectorTypes } from '../../../common/types/domain'; import { CASE_CONFIGURE_SAVED_OBJECT, SECURITY_SOLUTION_OWNER } from '../../../common/constants'; import { savedObjectsClientMock } from '@kbn/core/server/mocks'; import type { @@ -43,7 +43,22 @@ const basicConfigFields = { email: 'testemail@elastic.co', username: 'elastic', }, - customFields: [], + customFields: [ + { + type: CustomFieldTypes.TOGGLE as const, + key: 'toggle_custom_field', + label: 'Toggle', + required: true, + defaultValue: true, + }, + { + type: CustomFieldTypes.TEXT as const, + key: 'text_custom_field', + label: 'Text', + required: true, + defaultValue: 'foobar', + }, + ], }; const createConfigUpdateParams = (connector?: CaseConnector): Partial => ({ @@ -172,7 +187,22 @@ describe('CaseConfigureService', () => { "full_name": "elastic", "username": "elastic", }, - "customFields": Array [], + "customFields": Array [ + Object { + "defaultValue": true, + "key": "toggle_custom_field", + "label": "Toggle", + "required": true, + "type": "toggle", + }, + Object { + "defaultValue": "foobar", + "key": "text_custom_field", + "label": "Text", + "required": true, + "type": "text", + }, + ], "owner": "securitySolution", "updated_at": "2020-04-09T09:43:51.778Z", "updated_by": Object { @@ -443,7 +473,22 @@ describe('CaseConfigureService', () => { "full_name": "elastic", "username": "elastic", }, - "customFields": Array [], + "customFields": Array [ + Object { + "defaultValue": true, + "key": "toggle_custom_field", + "label": "Toggle", + "required": true, + "type": "toggle", + }, + Object { + "defaultValue": "foobar", + "key": "text_custom_field", + "label": "Text", + "required": true, + "type": "text", + }, + ], "owner": "securitySolution", "updated_at": "2020-04-09T09:43:51.778Z", "updated_by": Object { 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 7636d38b681e1..a9af52c9a6b31 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 @@ -77,7 +77,10 @@ export class CloudFullStoryPlugin implements Plugin { ...(pageVarsDebounceTime ? { pageVarsDebounceTimeMs: duration(pageVarsDebounceTime).asMilliseconds() } : {}), - // Load an Elastic-internally audited script. Ideally, it should be hosted on a CDN. + /** + * FIXME: this should use the {@link IStaticAssets['getPluginAssetHref']} + * function. Then we can avoid registering our own endpoint in this plugin. + */ scriptUrl: basePath.prepend( `/internal/cloud/${this.initializerContext.env.packageInfo.buildNum}/fullstory.js` ), diff --git a/x-pack/plugins/cloud_security_posture/common/types/rules/v4.ts b/x-pack/plugins/cloud_security_posture/common/types/rules/v4.ts index 78680bf111dc7..33134eed32e38 100644 --- a/x-pack/plugins/cloud_security_posture/common/types/rules/v4.ts +++ b/x-pack/plugins/cloud_security_posture/common/types/rules/v4.ts @@ -120,6 +120,7 @@ export interface BenchmarkRuleSelectParams { export interface PageUrlParams { benchmarkId: BenchmarksCisId; benchmarkVersion: string; + ruleId?: string; } export const rulesToUpdate = schema.arrayOf( diff --git a/x-pack/plugins/cloud_security_posture/public/common/navigation/constants.ts b/x-pack/plugins/cloud_security_posture/public/common/navigation/constants.ts index 674a28f34e97c..7390d2d846f11 100644 --- a/x-pack/plugins/cloud_security_posture/public/common/navigation/constants.ts +++ b/x-pack/plugins/cloud_security_posture/public/common/navigation/constants.ts @@ -63,7 +63,7 @@ export const cloudPosturePages: Record = { export const benchmarksNavigation: Record = { rules: { name: NAV_ITEMS_NAMES.RULES, - path: `${CLOUD_SECURITY_POSTURE_BASE_PATH}/benchmarks/:benchmarkId/:benchmarkVersion/rules`, + path: `${CLOUD_SECURITY_POSTURE_BASE_PATH}/benchmarks/:benchmarkId/:benchmarkVersion/rules/:ruleId?`, id: 'cloud_security_posture-benchmarks-rules', }, }; 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 d402ea2939062..f2881c1798883 100644 --- a/x-pack/plugins/cloud_security_posture/public/common/types.ts +++ b/x-pack/plugins/cloud_security_posture/public/common/types.ts @@ -17,6 +17,10 @@ export interface FindingsBaseURLQuery { * Filters that are part of the query but not persisted in the URL or in the Filter Manager */ nonPersistedFilters?: Filter[]; + /** + * Grouping component selection + */ + groupBy?: string[]; } export interface FindingsBaseESQueryConfig { diff --git a/x-pack/plugins/cloud_security_posture/public/components/cloud_security_grouping/use_cloud_security_grouping.ts b/x-pack/plugins/cloud_security_posture/public/components/cloud_security_grouping/use_cloud_security_grouping.ts index 05dc555bc3f7d..cd45c28f90d8a 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/cloud_security_grouping/use_cloud_security_grouping.ts +++ b/x-pack/plugins/cloud_security_posture/public/components/cloud_security_grouping/use_cloud_security_grouping.ts @@ -69,8 +69,11 @@ export const useCloudSecurityGrouping = ({ groupingId: groupingLocalStorageKey, maxGroupingLevels, title: groupingTitle, - onGroupChange: () => { + onGroupChange: ({ groupByFields }) => { setActivePageIndex(0); + setUrlQuery({ + groupBy: groupByFields, + }); }, }); @@ -85,6 +88,16 @@ export const useCloudSecurityGrouping = ({ setActivePageIndex(0); }, [urlQuery.filters, urlQuery.query]); + /** + * Set the selected groups from the URL query on the initial render + */ + useEffect(() => { + if (urlQuery.groupBy) { + grouping.setSelectedGroups(urlQuery.groupBy); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + // This is recommended by the grouping component to cover an edge case // where the selectedGroup has multiple values const uniqueValue = useMemo(() => `${selectedGroup}-${uuid.v4()}`, [selectedGroup]); diff --git a/x-pack/plugins/cloud_security_posture/public/components/column_name_with_tooltip.tsx b/x-pack/plugins/cloud_security_posture/public/components/column_name_with_tooltip.tsx index e0b3da860a1cd..24f57dadea80a 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/column_name_with_tooltip.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/column_name_with_tooltip.tsx @@ -15,14 +15,14 @@ export const ColumnNameWithTooltip = ({ tooltipContent: EuiToolTipProps['content']; columnName: ReactNode; }) => ( - - - - {columnName} - - - - - - + + + {columnName} + + + + + + + ); diff --git a/x-pack/plugins/cloud_security_posture/public/components/vulnerability_badges.tsx b/x-pack/plugins/cloud_security_posture/public/components/vulnerability_badges.tsx index ff8924833a294..25a77b3328768 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/vulnerability_badges.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/vulnerability_badges.tsx @@ -24,8 +24,10 @@ interface SeverityStatusBadgeProps { export const CVSScoreBadge = ({ score, version }: CVSScoreBadgeProps) => { if (!score) return null; + const color = getCvsScoreColor(score); const versionDisplay = version ? `v${version.split('.')[0]}` : null; + return ( { + 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, + }), + }); + switch (tab.id) { case 'overview': - return ; + return ; case 'rule': - return ; + return ; case 'table': return ; case 'json': 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 e6e5b1386652d..42808c02f5050 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,6 +13,7 @@ import { EuiPanel, EuiSpacer, EuiText, + EuiToolTip, } from '@elastic/eui'; import React, { useMemo } from 'react'; import moment from 'moment'; @@ -36,12 +37,21 @@ import { FindingsDetectionRuleCounter } from './findings_detection_rule_counter' type Accordion = Pick & Pick; -const getDetailsList = (data: CspFinding, discoverIndexLink: string | undefined) => [ +const getDetailsList = (data: CspFinding, ruleFlyoutLink: string, discoverIndexLink?: string) => [ { title: i18n.translate('xpack.csp.findings.findingsFlyout.overviewTab.ruleNameTitle', { defaultMessage: 'Rule Name', }), - description: data.rule.name, + description: ( + + {data.rule.name} + + ), }, { title: i18n.translate('xpack.csp.findings.findingsFlyout.overviewTab.alertsTitle', { @@ -160,10 +170,14 @@ const getEvidenceList = ({ result }: CspFinding) => }, ].filter(truthy); -export const OverviewTab = ({ data }: { data: CspFinding }) => { - const { - services: { discover }, - } = useKibana(); +export const OverviewTab = ({ + data, + ruleFlyoutLink, +}: { + data: CspFinding; + ruleFlyoutLink: string; +}) => { + const { discover } = useKibana().services; const latestFindingsDataView = useLatestFindingsDataView(LATEST_FINDINGS_INDEX_PATTERN); const discoverIndexLink = useMemo( @@ -185,7 +199,7 @@ export const OverviewTab = ({ data }: { data: CspFinding }) => { defaultMessage: 'Details', }), id: 'detailsAccordion', - listItems: getDetailsList(data, discoverIndexLink), + listItems: getDetailsList(data, ruleFlyoutLink, discoverIndexLink), }, { initialIsOpen: true, @@ -206,7 +220,7 @@ export const OverviewTab = ({ data }: { data: CspFinding }) => { listItems: getEvidenceList(data), }, ].filter(truthy), - [data, discoverIndexLink, hasEvidence] + [data, discoverIndexLink, 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 816717943bddd..9b7b400a58196 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,18 +5,35 @@ * 2.0. */ -import { EuiBadge, EuiDescriptionList } from '@elastic/eui'; +import { EuiBadge, EuiDescriptionList, EuiLink, EuiToolTip } 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'; -export const getRuleList = (rule: CspFinding['rule']) => [ +export const getRuleList = ( + rule: CspFinding['rule'], + ruleState = 'unmuted', + ruleFlyoutLink?: string +) => [ { title: i18n.translate('xpack.csp.findings.findingsFlyout.ruleTab.nameTitle', { defaultMessage: 'Name', }), - description: rule.name, + description: ruleFlyoutLink ? ( + + {rule.name} + + ) : ( + rule.name + ), }, { title: i18n.translate('xpack.csp.findings.findingsFlyout.ruleTab.descriptionTitle', { @@ -24,6 +41,20 @@ export const getRuleList = (rule: CspFinding['rule']) => [ }), description: {rule.description}, }, + { + title: i18n.translate('xpack.csp.findings.findingsFlyout.ruleTab.AlertsTitle', { + defaultMessage: 'Alerts', + }), + description: + ruleState === 'unmuted' ? ( + + ) : ( + + ), + }, { title: i18n.translate('xpack.csp.findings.findingsFlyout.ruleTab.tagsTitle', { defaultMessage: 'Tags', @@ -80,6 +111,6 @@ export const getRuleList = (rule: CspFinding['rule']) => [ : []), ]; -export const RuleTab = ({ data }: { data: CspFinding }) => ( - -); +export const RuleTab = ({ data, ruleFlyoutLink }: { data: CspFinding; ruleFlyoutLink: string }) => { + return ; +}; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/utils/create_detection_rule_from_benchmark.ts b/x-pack/plugins/cloud_security_posture/public/pages/configurations/utils/create_detection_rule_from_benchmark.ts new file mode 100644 index 0000000000000..f445761ed8447 --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/public/pages/configurations/utils/create_detection_rule_from_benchmark.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 { HttpSetup } from '@kbn/core/public'; +import { CspBenchmarkRule } from '../../../../common/types/latest'; +import { + FINDINGS_INDEX_PATTERN, + LATEST_FINDINGS_RETENTION_POLICY, +} from '../../../../common/constants'; +import { createDetectionRule } from '../../../common/api/create_detection_rule'; +import { generateBenchmarkRuleTags } from '../../../../common/utils/detection_rules'; + +const DEFAULT_RULE_RISK_SCORE = 0; +const DEFAULT_RULE_SEVERITY = 'low'; +const DEFAULT_RULE_ENABLED = true; +const DEFAULT_RULE_AUTHOR = 'Elastic'; +const DEFAULT_RULE_LICENSE = 'Elastic License v2'; +const DEFAULT_MAX_ALERTS_PER_RULE = 100; +const ALERT_SUPPRESSION_FIELD = 'resource.id'; +const ALERT_TIMESTAMP_FIELD = 'event.ingested'; +const DEFAULT_INVESTIGATION_FIELDS = { + field_names: ['resource.name', 'resource.id', 'resource.type', 'resource.sub_type'], +}; + +enum AlertSuppressionMissingFieldsStrategy { + // per each document a separate alert will be created + DoNotSuppress = 'doNotSuppress', + // only one alert will be created per suppress by bucket + Suppress = 'suppress', +} + +const convertReferencesLinksToArray = (input: string | undefined) => { + if (!input) { + return []; + } + // Match all URLs in the input string using a regular expression + const matches = input.match(/(https?:\/\/\S+)/g); + + if (!matches) { + return []; + } + + // Remove the numbers and new lines + return matches.map((link) => link.replace(/^\d+\. /, '').replace(/\n/g, '')); +}; + +const generateFindingsRuleQuery = (benchmarkRule: CspBenchmarkRule['metadata']) => { + const currentTimestamp = new Date().toISOString(); + + return `rule.benchmark.rule_number: "${benchmarkRule.benchmark.rule_number}" + AND rule.benchmark.id: "${benchmarkRule.benchmark.id}" + AND result.evaluation: "failed" + AND event.ingested >= "${currentTimestamp}"`; +}; + +/* + * Creates a detection rule from a Benchmark rule + */ +export const createDetectionRuleFromBenchmark = async ( + http: HttpSetup, + benchmarkRule: CspBenchmarkRule['metadata'] +) => { + return await createDetectionRule({ + http, + rule: { + type: 'query', + language: 'kuery', + license: DEFAULT_RULE_LICENSE, + author: [DEFAULT_RULE_AUTHOR], + filters: [], + false_positives: [], + risk_score: DEFAULT_RULE_RISK_SCORE, + risk_score_mapping: [], + severity: DEFAULT_RULE_SEVERITY, + severity_mapping: [], + threat: [], + interval: '1h', + from: `now-${LATEST_FINDINGS_RETENTION_POLICY}`, + to: 'now', + max_signals: DEFAULT_MAX_ALERTS_PER_RULE, + timestamp_override: ALERT_TIMESTAMP_FIELD, + timestamp_override_fallback_disabled: false, + actions: [], + enabled: DEFAULT_RULE_ENABLED, + alert_suppression: { + group_by: [ALERT_SUPPRESSION_FIELD], + missing_fields_strategy: AlertSuppressionMissingFieldsStrategy.Suppress, + }, + index: [FINDINGS_INDEX_PATTERN], + query: generateFindingsRuleQuery(benchmarkRule), + references: convertReferencesLinksToArray(benchmarkRule.references), + name: benchmarkRule.name, + description: benchmarkRule.rationale, + tags: generateBenchmarkRuleTags(benchmarkRule), + investigation_fields: DEFAULT_INVESTIGATION_FIELDS, + note: benchmarkRule.remediation, + }, + }); +}; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_container.tsx b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_container.tsx index 62d1371b01f3f..d50a351a0f1b6 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_container.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_container.tsx @@ -4,10 +4,11 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import React, { useState, useMemo } from 'react'; +import React, { useState, useMemo, useEffect } from 'react'; import compareVersions from 'compare-versions'; import { EuiSpacer } from '@elastic/eui'; -import { useParams } from 'react-router-dom'; +import { useParams, useHistory, generatePath } from 'react-router-dom'; +import { benchmarksNavigation } from '../../common/navigation/constants'; import { buildRuleKey } from '../../../common/utils/rules_states'; import { extractErrorMessage } from '../../../common/utils/helpers'; import { RulesTable } from './rules_table'; @@ -41,7 +42,10 @@ interface RulesPageData { export type RulesState = RulesPageData & RulesQuery; -const getRulesPage = ( +const getPage = (data: CspBenchmarkRulesWithStates[], { page, perPage }: RulesQuery) => + data.slice(page * perPage, (page + 1) * perPage); + +const getRulesPageData = ( data: CspBenchmarkRulesWithStates[], status: string, error: unknown, @@ -59,17 +63,45 @@ const getRulesPage = ( }; }; -const getPage = (data: CspBenchmarkRulesWithStates[], { page, perPage }: RulesQuery) => - data.slice(page * perPage, (page + 1) * perPage); - const MAX_ITEMS_PER_PAGE = 10000; export const RulesContainer = () => { const params = useParams(); - const [selectedRuleId, setSelectedRuleId] = useState(null); + const history = useHistory(); const [enabledDisabledItemsFilter, setEnabledDisabledItemsFilter] = useState('no-filter'); const { pageSize, setPageSize } = usePageSize(LOCAL_STORAGE_PAGE_SIZE_RULES_KEY); + const navToRuleFlyout = (ruleId: string) => { + history.push( + generatePath(benchmarksNavigation.rules.path, { + benchmarkVersion: params.benchmarkVersion, + benchmarkId: params.benchmarkId, + ruleId, + }) + ); + }; + + const navToRulePage = () => { + history.push( + generatePath(benchmarksNavigation.rules.path, { + benchmarkVersion: params.benchmarkVersion, + benchmarkId: params.benchmarkId, + }) + ); + }; + + // We need to make this call without filters. this way the section list is always full + const allRules = useFindCspBenchmarkRule( + { + page: 1, + perPage: MAX_ITEMS_PER_PAGE, + sortField: 'metadata.benchmark.rule_number', + sortOrder: 'asc', + }, + params.benchmarkId, + params.benchmarkVersion + ); + const [rulesQuery, setRulesQuery] = useState({ section: undefined, ruleNumber: undefined, @@ -80,6 +112,30 @@ export const RulesContainer = () => { sortOrder: 'asc', }); + // This useEffect is in charge of auto paginating to the correct page of a rule from the url params + useEffect(() => { + const getPageByRuleId = () => { + if (params.ruleId && allRules.data?.items) { + const ruleIndex = allRules.data.items.findIndex( + (rule) => rule.metadata.id === params.ruleId + ); + + if (ruleIndex !== -1) { + // Calculate the page based on the rule index and page size + const rulePage = Math.floor(ruleIndex / pageSize); + return rulePage; + } + } + return 0; + }; + + setRulesQuery({ + ...rulesQuery, + page: getPageByRuleId(), + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [allRules.data?.items]); + const { data, status, error } = useFindCspBenchmarkRule( { section: rulesQuery.section, @@ -94,27 +150,9 @@ export const RulesContainer = () => { params.benchmarkVersion ); - // We need to make this call again without the filters. this way the section list is always full - const allRules = useFindCspBenchmarkRule( - { - page: 1, - perPage: MAX_ITEMS_PER_PAGE, - sortField: 'metadata.benchmark.rule_number', - sortOrder: 'asc', - }, - params.benchmarkId, - params.benchmarkVersion - ); - const rulesStates = useCspGetRulesStates(); const arrayRulesStates: RuleStateAttributes[] = Object.values(rulesStates.data || {}); - const filteredRulesStates: RuleStateAttributes[] = arrayRulesStates.filter( - (ruleState: RuleStateAttributes) => - ruleState.benchmark_id === params.benchmarkId && - ruleState.benchmark_version === 'v' + params.benchmarkVersion - ); - const rulesWithStates: CspBenchmarkRulesWithStates[] = useMemo(() => { if (!data) return []; @@ -162,7 +200,7 @@ export const RulesContainer = () => { const cleanedRuleNumberList = [...new Set(ruleNumberList)].sort(compareVersions); const rulesPageData = useMemo( - () => getRulesPage(filteredRulesWithStates, status, error, rulesQuery), + () => getRulesPageData(filteredRulesWithStates, status, error, rulesQuery), [filteredRulesWithStates, status, error, rulesQuery] ); @@ -175,13 +213,14 @@ export const RulesContainer = () => { const rulesFlyoutData: CspBenchmarkRulesWithStates = { ...{ state: - filteredRulesStates.find( - (filteredRuleState) => filteredRuleState.rule_id === selectedRuleId - )?.muted === true + arrayRulesStates.find((filteredRuleState) => filteredRuleState.rule_id === params.ruleId) + ?.muted === true ? 'muted' : 'unmuted', }, - ...{ metadata: rulesPageData.rules_map.get(selectedRuleId!)?.metadata! }, + ...{ + metadata: allRules.data?.items.find((rule) => rule.metadata.id === params.ruleId)?.metadata!, + }, }; return ( @@ -227,16 +266,16 @@ export const RulesContainer = () => { setPageSize(paginationQuery.perPage); setRulesQuery((currentQuery) => ({ ...currentQuery, ...paginationQuery })); }} - setSelectedRuleId={setSelectedRuleId} - selectedRuleId={selectedRuleId} + selectedRuleId={params.ruleId} + onRuleClick={navToRuleFlyout} refetchRulesStates={rulesStates.refetch} selectedRules={selectedRules} setSelectedRules={setSelectedRules} /> - {selectedRuleId && ( + {params.ruleId && rulesFlyoutData.metadata && ( setSelectedRuleId(null)} + onClose={navToRulePage} refetchRulesStates={rulesStates.refetch} /> )} diff --git a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_detection_rule_counter.tsx b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_detection_rule_counter.tsx new file mode 100644 index 0000000000000..04b6a4ab83597 --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_detection_rule_counter.tsx @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { HttpSetup } from '@kbn/core/public'; +import React from 'react'; +import { CspBenchmarkRule } from '../../../common/types/latest'; +import { getFindingsDetectionRuleSearchTags } from '../../../common/utils/detection_rules'; +import { DetectionRuleCounter } from '../../components/detection_rule_counter'; +import { createDetectionRuleFromBenchmark } from '../configurations/utils/create_detection_rule_from_benchmark'; + +export const RulesDetectionRuleCounter = ({ + benchmarkRule, +}: { + benchmarkRule: CspBenchmarkRule['metadata']; +}) => { + const createBenchmarkRuleFn = async (http: HttpSetup) => + await createDetectionRuleFromBenchmark(http, benchmarkRule); + + return ( + + ); +}; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_flyout.tsx b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_flyout.tsx index dbfcb6df75a98..333f958de6fb1 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_flyout.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_flyout.tsx @@ -17,9 +17,12 @@ import { EuiFlexGroup, EuiSwitch, EuiFlyoutFooter, + EuiIcon, + EuiToolTip, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; import { CspBenchmarkRuleMetadata } from '../../../common/types/latest'; import { getRuleList } from '../configurations/findings_flyout/rule_tab'; import { getRemediationList } from '../configurations/findings_flyout/overview_tab'; @@ -73,6 +76,7 @@ export const RuleFlyout = ({ onClose, rule, refetchRulesStates }: RuleFlyoutProp await refetchRulesStates(); } }; + return ( @@ -144,9 +148,32 @@ const RuleOverviewTab = ({ const ruleState = (rule: CspBenchmarkRulesWithStates, switchRuleStates: () => Promise) => [ { - title: i18n.translate('xpack.csp.rules.rulesFlyout.ruleState', { - defaultMessage: 'Enabled', - }), + title: ( + + + + + + + + + + + ), description: ( <> & { setPagination(pagination: Pick): void; - setSelectedRuleId(id: string | null): void; - selectedRuleId: string | null; + onRuleClick: (ruleID: string) => void; + selectedRuleId?: string; refetchRulesStates: () => void; selectedRules: CspBenchmarkRulesWithStates[]; setSelectedRules: (rules: CspBenchmarkRulesWithStates[]) => void; @@ -42,7 +43,7 @@ type RulesTableProps = Pick< type GetColumnProps = Pick< RulesTableProps, - 'setSelectedRuleId' | 'refetchRulesStates' | 'selectedRules' | 'setSelectedRules' + 'onRuleClick' | 'refetchRulesStates' | 'selectedRules' | 'setSelectedRules' > & { postRequestChangeRulesStates: ( actionOnRule: 'mute' | 'unmute', @@ -59,7 +60,6 @@ type GetColumnProps = Pick< export const RulesTable = ({ setPagination, - setSelectedRuleId, perPage: pageSize, rules_page: items, page, @@ -70,6 +70,7 @@ export const RulesTable = ({ refetchRulesStates, selectedRules, setSelectedRules, + onRuleClick, onSortChange, }: RulesTableProps) => { const { euiTheme } = useEuiTheme(); @@ -133,7 +134,6 @@ export const RulesTable = ({ const columns = useMemo( () => getColumns({ - setSelectedRuleId, refetchRulesStates, postRequestChangeRulesStates, selectedRules, @@ -142,15 +142,16 @@ export const RulesTable = ({ setIsAllRulesSelectedThisPage, isAllRulesSelectedThisPage, isCurrentPageRulesASubset, + onRuleClick, }), [ - setSelectedRuleId, refetchRulesStates, postRequestChangeRulesStates, selectedRules, setSelectedRules, items, isAllRulesSelectedThisPage, + onRuleClick, ] ); @@ -173,7 +174,6 @@ export const RulesTable = ({ }; const getColumns = ({ - setSelectedRuleId, refetchRulesStates, postRequestChangeRulesStates, selectedRules, @@ -181,6 +181,7 @@ const getColumns = ({ items, isAllRulesSelectedThisPage, isCurrentPageRulesASubset, + onRuleClick, }: GetColumnProps): Array> => [ { field: 'action', @@ -255,7 +256,7 @@ const getColumns = ({ title={name} onClick={(e: React.MouseEvent) => { e.stopPropagation(); - setSelectedRuleId(rule.metadata.id); + onRuleClick(rule.metadata.id); }} data-test-subj={TEST_SUBJECTS.CSP_RULES_TABLE_ROW_ITEM_NAME} > @@ -272,9 +273,16 @@ const getColumns = ({ }, { field: 'metadata.name', - name: i18n.translate('xpack.csp.rules.rulesTable.mutedColumnLabel', { - defaultMessage: 'Enabled', - }), + name: ( + + ), align: 'right', width: '100px', truncateText: true, diff --git a/x-pack/plugins/ecs_data_quality_dashboard/kibana.jsonc b/x-pack/plugins/ecs_data_quality_dashboard/kibana.jsonc index 2650184783066..5adbe3eeee830 100644 --- a/x-pack/plugins/ecs_data_quality_dashboard/kibana.jsonc +++ b/x-pack/plugins/ecs_data_quality_dashboard/kibana.jsonc @@ -1,7 +1,7 @@ { "type": "plugin", "id": "@kbn/ecs-data-quality-dashboard-plugin", - "owner": "@elastic/security-threat-hunting-investigations", + "owner": "@elastic/security-threat-hunting-explore", "description": "APIs used to assess the quality of data in Elasticsearch indexes", "plugin": { "id": "ecsDataQualityDashboard", diff --git a/x-pack/plugins/ecs_data_quality_dashboard/server/lib/data_stream/results_field_map.ts b/x-pack/plugins/ecs_data_quality_dashboard/server/lib/data_stream/results_field_map.ts index 59f8ade6cb834..c0b929ec6deb8 100644 --- a/x-pack/plugins/ecs_data_quality_dashboard/server/lib/data_stream/results_field_map.ts +++ b/x-pack/plugins/ecs_data_quality_dashboard/server/lib/data_stream/results_field_map.ts @@ -8,33 +8,23 @@ import type { FieldMap } from '@kbn/data-stream-adapter'; export const resultsFieldMap: FieldMap = { - 'meta.batchId': { type: 'keyword', required: true }, - 'meta.ecsVersion': { type: 'keyword', required: true }, - 'meta.errorCount': { type: 'long', required: true }, - 'meta.ilmPhase': { type: 'keyword', required: true }, - 'meta.indexId': { type: 'keyword', required: true }, - 'meta.indexName': { type: 'keyword', required: true }, - 'meta.isCheckAll': { type: 'boolean', required: true }, - 'meta.numberOfDocuments': { type: 'long', required: true }, - 'meta.numberOfFields': { type: 'long', required: true }, - 'meta.numberOfIncompatibleFields': { type: 'long', required: true }, - 'meta.numberOfEcsFields': { type: 'long', required: true }, - 'meta.numberOfCustomFields': { type: 'long', required: true }, - 'meta.numberOfIndices': { type: 'long', required: true }, - 'meta.numberOfIndicesChecked': { type: 'long', required: true }, - 'meta.numberOfSameFamily': { type: 'long', required: true }, - 'meta.sameFamilyFields': { type: 'keyword', required: true, array: true }, - 'meta.sizeInBytes': { type: 'long', required: true }, - 'meta.timeConsumedMs': { type: 'long', required: true }, - 'meta.unallowedMappingFields': { type: 'keyword', required: true, array: true }, - 'meta.unallowedValueFields': { type: 'keyword', required: true, array: true }, - 'rollup.docsCount': { type: 'long', required: true }, - 'rollup.error': { type: 'text', required: false }, - 'rollup.ilmExplainPhaseCounts': { type: 'object', required: false }, - 'rollup.indices': { type: 'long', required: true }, - 'rollup.pattern': { type: 'keyword', required: true }, - 'rollup.sizeInBytes': { type: 'long', required: true }, - 'rollup.ilmExplain': { type: 'object', required: true, array: true }, - 'rollup.stats': { type: 'object', required: true, array: true }, - 'rollup.results': { type: 'object', required: true, array: true }, + batchId: { type: 'keyword', required: true }, + indexName: { type: 'keyword', required: true }, + isCheckAll: { type: 'boolean', required: true }, + checkedAt: { type: 'date', required: true }, + docsCount: { type: 'long', required: true }, + totalFieldCount: { type: 'long', required: true }, + ecsFieldCount: { type: 'long', required: true }, + customFieldCount: { type: 'long', required: true }, + incompatibleFieldCount: { type: 'long', required: true }, + sameFamilyFieldCount: { type: 'long', required: true }, + sameFamilyFields: { type: 'keyword', required: true, array: true }, + unallowedMappingFields: { type: 'keyword', required: true, array: true }, + unallowedValueFields: { type: 'keyword', required: true, array: true }, + sizeInBytes: { type: 'long', required: true }, + ilmPhase: { type: 'keyword', required: true }, + markdownComments: { type: 'text', required: true, array: true }, + ecsVersion: { type: 'keyword', required: true }, + indexId: { type: 'keyword', required: true }, + error: { type: 'text', required: false }, }; diff --git a/x-pack/plugins/ecs_data_quality_dashboard/server/plugin.ts b/x-pack/plugins/ecs_data_quality_dashboard/server/plugin.ts index 19c6f12479694..cb6fe9da7c276 100755 --- a/x-pack/plugins/ecs_data_quality_dashboard/server/plugin.ts +++ b/x-pack/plugins/ecs_data_quality_dashboard/server/plugin.ts @@ -48,14 +48,13 @@ export class EcsDataQualityDashboardPlugin public setup(core: CoreSetup, plugins: PluginSetupDependencies) { this.logger.debug('ecsDataQualityDashboard: Setup'); - // TODO: Uncomment https://github.com/elastic/kibana/pull/173185#issuecomment-1908034302 - // this.resultsDataStream.install({ - // esClient: core - // .getStartServices() - // .then(([{ elasticsearch }]) => elasticsearch.client.asInternalUser), - // logger: this.logger, - // pluginStop$: this.pluginStop$, - // }); + this.resultsDataStream.install({ + esClient: core + .getStartServices() + .then(([{ elasticsearch }]) => elasticsearch.client.asInternalUser), + logger: this.logger, + pluginStop$: this.pluginStop$, + }); core.http.registerRouteHandlerContext< DataQualityDashboardRequestHandlerContext, diff --git a/x-pack/plugins/ecs_data_quality_dashboard/server/routes/get_ilm_explain.ts b/x-pack/plugins/ecs_data_quality_dashboard/server/routes/get_ilm_explain.ts index 73282d11e3d71..31202adffed2c 100644 --- a/x-pack/plugins/ecs_data_quality_dashboard/server/routes/get_ilm_explain.ts +++ b/x-pack/plugins/ecs_data_quality_dashboard/server/routes/get_ilm_explain.ts @@ -19,6 +19,7 @@ export const getILMExplainRoute = (router: IRouter, logger: Logger) => { .get({ path: GET_ILM_EXPLAIN, access: 'internal', + options: { tags: ['access:securitySolution'] }, }) .addVersion( { diff --git a/x-pack/plugins/ecs_data_quality_dashboard/server/routes/get_index_mappings.ts b/x-pack/plugins/ecs_data_quality_dashboard/server/routes/get_index_mappings.ts index e593320933f7c..f3c59ccf9f3e2 100755 --- a/x-pack/plugins/ecs_data_quality_dashboard/server/routes/get_index_mappings.ts +++ b/x-pack/plugins/ecs_data_quality_dashboard/server/routes/get_index_mappings.ts @@ -19,6 +19,7 @@ export const getIndexMappingsRoute = (router: IRouter, logger: Logger) => { .get({ path: GET_INDEX_MAPPINGS, access: 'internal', + options: { tags: ['access:securitySolution'] }, }) .addVersion( { diff --git a/x-pack/plugins/ecs_data_quality_dashboard/server/routes/get_index_stats.ts b/x-pack/plugins/ecs_data_quality_dashboard/server/routes/get_index_stats.ts index cbaf7940a4b51..69d49b8611101 100644 --- a/x-pack/plugins/ecs_data_quality_dashboard/server/routes/get_index_stats.ts +++ b/x-pack/plugins/ecs_data_quality_dashboard/server/routes/get_index_stats.ts @@ -20,6 +20,7 @@ export const getIndexStatsRoute = (router: IRouter, logger: Logger) => { .get({ path: GET_INDEX_STATS, access: 'internal', + options: { tags: ['access:securitySolution'] }, }) .addVersion( { diff --git a/x-pack/plugins/ecs_data_quality_dashboard/server/routes/results/get_results.test.ts b/x-pack/plugins/ecs_data_quality_dashboard/server/routes/results/get_results.test.ts index 44f7a97abf0d0..05a714a27275a 100644 --- a/x-pack/plugins/ecs_data_quality_dashboard/server/routes/results/get_results.test.ts +++ b/x-pack/plugins/ecs_data_quality_dashboard/server/routes/results/get_results.test.ts @@ -12,19 +12,17 @@ import { requestContextMock } from '../../__mocks__/request_context'; import type { LatestAggResponseBucket } from './get_results'; import { getResultsRoute, getQuery } from './get_results'; import { loggerMock, type MockedLogger } from '@kbn/logging-mocks'; -import { resultBody, resultDocument } from './results.mock'; -import type { - SearchResponse, - SecurityHasPrivilegesResponse, -} from '@elastic/elasticsearch/lib/api/types'; +import { resultDocument } from './results.mock'; +import type { SearchResponse } from '@elastic/elasticsearch/lib/api/types'; import type { ResultDocument } from '../../schemas/result'; +import type { CheckIndicesPrivilegesParam } from './privileges'; const searchResponse = { aggregations: { latest: { buckets: [ { - key: 'logs-*', + key: resultDocument.indexName, latest_doc: { hits: { hits: [{ _source: resultDocument }] } }, }, ], @@ -35,8 +33,15 @@ const searchResponse = { Record >; -// TODO: https://github.com/elastic/kibana/pull/173185#issuecomment-1908034302 -describe.skip('getResultsRoute route', () => { +const mockCheckIndicesPrivileges = jest.fn(({ indices }: CheckIndicesPrivilegesParam) => + Promise.resolve(Object.fromEntries(indices.map((index) => [index, true]))) +); +jest.mock('./privileges', () => ({ + checkIndicesPrivileges: (params: CheckIndicesPrivilegesParam) => + mockCheckIndicesPrivileges(params), +})); + +describe('getResultsRoute route', () => { describe('querying', () => { let server: ReturnType; let { context } = requestContextMock.createTools(); @@ -45,7 +50,7 @@ describe.skip('getResultsRoute route', () => { const req = requestMock.create({ method: 'get', path: RESULTS_ROUTE_PATH, - query: { patterns: 'logs-*,alerts-*' }, + query: { pattern: 'logs-*' }, }); beforeEach(() => { @@ -56,9 +61,9 @@ describe.skip('getResultsRoute route', () => { ({ context } = requestContextMock.createTools()); - context.core.elasticsearch.client.asCurrentUser.security.hasPrivileges.mockResolvedValue({ - index: { 'logs-*': { all: true }, 'alerts-*': { all: true } }, - } as unknown as SecurityHasPrivilegesResponse); + context.core.elasticsearch.client.asInternalUser.indices.get.mockResolvedValue({ + [resultDocument.indexName]: {}, + }); getResultsRoute(server.router, logger); }); @@ -68,10 +73,13 @@ describe.skip('getResultsRoute route', () => { mockSearch.mockResolvedValueOnce(searchResponse); const response = await server.inject(req, requestContextMock.convertContext(context)); - expect(mockSearch).toHaveBeenCalled(); + expect(mockSearch).toHaveBeenCalledWith({ + index: expect.any(String), + ...getQuery([resultDocument.indexName]), + }); expect(response.status).toEqual(200); - expect(response.body).toEqual([{ '@timestamp': expect.any(Number), ...resultBody }]); + expect(response.body).toEqual([resultDocument]); }); it('handles results data stream error', async () => { @@ -99,7 +107,7 @@ describe.skip('getResultsRoute route', () => { }); }); - describe('request pattern authorization', () => { + describe('request indices authorization', () => { let server: ReturnType; let { context } = requestContextMock.createTools(); let logger: MockedLogger; @@ -107,7 +115,7 @@ describe.skip('getResultsRoute route', () => { const req = requestMock.create({ method: 'get', path: RESULTS_ROUTE_PATH, - query: { patterns: 'logs-*,alerts-*' }, + query: { pattern: 'logs-*' }, }); beforeEach(() => { @@ -120,54 +128,69 @@ describe.skip('getResultsRoute route', () => { context.core.elasticsearch.client.asInternalUser.search.mockResolvedValue(searchResponse); - context.core.elasticsearch.client.asCurrentUser.security.hasPrivileges.mockResolvedValue({ - index: { 'logs-*': { all: true }, 'alerts-*': { all: true } }, - } as unknown as SecurityHasPrivilegesResponse); + context.core.elasticsearch.client.asInternalUser.indices.get.mockResolvedValue({ + [resultDocument.indexName]: {}, + }); getResultsRoute(server.router, logger); }); - it('should authorize pattern', async () => { - const mockHasPrivileges = - context.core.elasticsearch.client.asCurrentUser.security.hasPrivileges; - mockHasPrivileges.mockResolvedValueOnce({ - index: { 'logs-*': { all: true }, 'alerts-*': { all: true } }, - } as unknown as SecurityHasPrivilegesResponse); + it('should authorize indices from pattern', async () => { + const mockGetIndices = context.core.elasticsearch.client.asInternalUser.indices.get; + mockGetIndices.mockResolvedValueOnce({ [resultDocument.indexName]: {} }); const response = await server.inject(req, requestContextMock.convertContext(context)); - expect(mockHasPrivileges).toHaveBeenCalledWith({ - index: [ - { names: ['logs-*', 'alerts-*'], privileges: ['all', 'read', 'view_index_metadata'] }, - ], - }); + expect(mockGetIndices).toHaveBeenCalledWith({ index: 'logs-*', features: 'aliases' }); + expect(mockCheckIndicesPrivileges).toHaveBeenCalledWith( + expect.objectContaining({ indices: [resultDocument.indexName] }) + ); expect(context.core.elasticsearch.client.asInternalUser.search).toHaveBeenCalled(); expect(response.status).toEqual(200); - expect(response.body).toEqual([{ '@timestamp': expect.any(Number), ...resultBody }]); + expect(response.body).toEqual([resultDocument]); }); - it('should search authorized patterns only', async () => { - const mockHasPrivileges = - context.core.elasticsearch.client.asCurrentUser.security.hasPrivileges; - mockHasPrivileges.mockResolvedValueOnce({ - index: { 'logs-*': { all: false }, 'alerts-*': { all: true } }, - } as unknown as SecurityHasPrivilegesResponse); + it('should authorize data streams from pattern', async () => { + const dataStreamName = 'test_data_stream_name'; + const resultIndexNameTwo = `${resultDocument.indexName}_2`; + const resultIndexNameThree = `${resultDocument.indexName}_3`; + const mockGetIndices = context.core.elasticsearch.client.asInternalUser.indices.get; + mockGetIndices.mockResolvedValueOnce({ + [resultDocument.indexName]: {}, + [resultIndexNameTwo]: { data_stream: dataStreamName }, + [resultIndexNameThree]: { data_stream: dataStreamName }, + }); const response = await server.inject(req, requestContextMock.convertContext(context)); + + expect(mockGetIndices).toHaveBeenCalledWith({ index: 'logs-*', features: 'aliases' }); + expect(mockCheckIndicesPrivileges).toHaveBeenCalledWith( + expect.objectContaining({ indices: [resultDocument.indexName, dataStreamName] }) + ); expect(context.core.elasticsearch.client.asInternalUser.search).toHaveBeenCalledWith({ index: expect.any(String), - ...getQuery(['alerts-*']), + ...getQuery([resultDocument.indexName, resultIndexNameTwo, resultIndexNameThree]), }); expect(response.status).toEqual(200); + expect(response.body).toEqual([resultDocument]); + }); + + it('should not search unknown indices', async () => { + const mockGetIndices = context.core.elasticsearch.client.asInternalUser.indices.get; + mockGetIndices.mockResolvedValueOnce({}); // empty object means no index is found + + const response = await server.inject(req, requestContextMock.convertContext(context)); + + expect(mockCheckIndicesPrivileges).not.toHaveBeenCalled(); + expect(context.core.elasticsearch.client.asInternalUser.search).not.toHaveBeenCalled(); + + expect(response.status).toEqual(200); + expect(response.body).toEqual([]); }); - it('should not search unauthorized patterns', async () => { - const mockHasPrivileges = - context.core.elasticsearch.client.asCurrentUser.security.hasPrivileges; - mockHasPrivileges.mockResolvedValueOnce({ - index: { 'logs-*': { all: false }, 'alerts-*': { all: false } }, - } as unknown as SecurityHasPrivilegesResponse); + it('should not search unauthorized indices', async () => { + mockCheckIndicesPrivileges.mockResolvedValueOnce({}); // empty object means no index is authorized const response = await server.inject(req, requestContextMock.convertContext(context)); expect(context.core.elasticsearch.client.asInternalUser.search).not.toHaveBeenCalled(); @@ -176,11 +199,19 @@ describe.skip('getResultsRoute route', () => { expect(response.body).toEqual([]); }); - it('handles pattern authorization error', async () => { + it('handles index discovery error', async () => { + const errorMessage = 'Error!'; + const mockGetIndices = context.core.elasticsearch.client.asInternalUser.indices.get; + mockGetIndices.mockRejectedValueOnce({ message: errorMessage }); + + const response = await server.inject(req, requestContextMock.convertContext(context)); + expect(response.status).toEqual(500); + expect(response.body).toEqual({ message: errorMessage, status_code: 500 }); + }); + + it('handles index authorization error', async () => { const errorMessage = 'Error!'; - const mockHasPrivileges = - context.core.elasticsearch.client.asCurrentUser.security.hasPrivileges; - mockHasPrivileges.mockRejectedValueOnce({ message: errorMessage }); + mockCheckIndicesPrivileges.mockRejectedValueOnce({ message: errorMessage }); const response = await server.inject(req, requestContextMock.convertContext(context)); expect(response.status).toEqual(500); diff --git a/x-pack/plugins/ecs_data_quality_dashboard/server/routes/results/get_results.ts b/x-pack/plugins/ecs_data_quality_dashboard/server/routes/results/get_results.ts index 56729c7a40ab7..6c410e88f3626 100644 --- a/x-pack/plugins/ecs_data_quality_dashboard/server/routes/results/get_results.ts +++ b/x-pack/plugins/ecs_data_quality_dashboard/server/routes/results/get_results.ts @@ -11,20 +11,18 @@ import { RESULTS_ROUTE_PATH, INTERNAL_API_VERSION } from '../../../common/consta import { buildResponse } from '../../lib/build_response'; import { buildRouteValidation } from '../../schemas/common'; import { GetResultQuery } from '../../schemas/result'; -import type { Result, ResultDocument } from '../../schemas/result'; +import type { ResultDocument } from '../../schemas/result'; import { API_DEFAULT_ERROR_MESSAGE } from '../../translations'; import type { DataQualityDashboardRequestHandlerContext } from '../../types'; -import { createResultFromDocument } from './parser'; import { API_RESULTS_INDEX_NOT_AVAILABLE } from './translations'; +import { checkIndicesPrivileges } from './privileges'; -export const getQuery = (patterns: string[]) => ({ +export const getQuery = (indexName: string[]) => ({ size: 0, - query: { - bool: { filter: [{ terms: { 'rollup.pattern': patterns } }] }, - }, + query: { bool: { filter: [{ terms: { indexName } }] } }, aggs: { latest: { - terms: { field: 'rollup.pattern', size: 10000 }, // big enough to get all patterns, but under `index.max_terms_count` (default 65536) + terms: { field: 'indexName', size: 10000 }, // big enough to get all indexNames, but under `index.max_terms_count` (default 65536) aggs: { latest_doc: { top_hits: { size: 1, sort: [{ '@timestamp': { order: 'desc' } }] } } }, }, }, @@ -51,10 +49,6 @@ export const getResultsRoute = ( validate: { request: { query: buildRouteValidation(GetResultQuery) } }, }, async (context, request, response) => { - // TODO: https://github.com/elastic/kibana/pull/173185#issuecomment-1908034302 - return response.ok({ body: [] }); - - // eslint-disable-next-line no-unreachable const services = await context.resolve(['core', 'dataQualityDashboard']); const resp = buildResponse(response); @@ -70,38 +64,71 @@ export const getResultsRoute = ( } try { - // Confirm user has authorization for the requested patterns - const { patterns } = request.query; - const userEsClient = services.core.elasticsearch.client.asCurrentUser; - const privileges = await userEsClient.security.hasPrivileges({ - index: [ - { names: patterns.split(','), privileges: ['all', 'read', 'view_index_metadata'] }, - ], + const { client } = services.core.elasticsearch; + const { pattern } = request.query; + + // Discover all indices for the pattern using internal user + const indicesResponse = await client.asInternalUser.indices.get({ + index: pattern, + features: 'aliases', // omit 'settings' and 'mappings' to reduce response size }); - const authorizedPatterns = Object.keys(privileges.index).filter((pattern) => - Object.values(privileges.index[pattern]).some((v) => v === true) - ); - if (authorizedPatterns.length === 0) { + + // map data streams to their backing indices and collect indices to authorize + const indicesToAuthorize: string[] = []; + const dataStreamIndices: Record = {}; + Object.entries(indicesResponse).forEach(([indexName, { data_stream: dataStream }]) => { + if (dataStream) { + if (!dataStreamIndices[dataStream]) { + dataStreamIndices[dataStream] = []; + } + dataStreamIndices[dataStream].push(indexName); + } else { + indicesToAuthorize.push(indexName); + } + }); + indicesToAuthorize.push(...Object.keys(dataStreamIndices)); + if (indicesToAuthorize.length === 0) { return response.ok({ body: [] }); } - // Get the latest result of each pattern - const query = { index, ...getQuery(authorizedPatterns) }; - const internalEsClient = services.core.elasticsearch.client.asInternalUser; + // check privileges for indices or data streams + const hasIndexPrivileges = await checkIndicesPrivileges({ + client, + indices: indicesToAuthorize, + }); + + // filter out unauthorized indices, and expand data streams backing indices + const authorizedIndexNames = Object.entries(hasIndexPrivileges).reduce( + (acc, [indexName, authorized]) => { + if (authorized) { + if (dataStreamIndices[indexName]) { + acc.push(...dataStreamIndices[indexName]); + } else { + acc.push(indexName); + } + } + return acc; + }, + [] + ); + if (authorizedIndexNames.length === 0) { + return response.ok({ body: [] }); + } - const { aggregations } = await internalEsClient.search< + // Get the latest result for each indexName + const query = { index, ...getQuery(authorizedIndexNames) }; + const { aggregations } = await client.asInternalUser.search< ResultDocument, Record >(query); - const results: Result[] = - aggregations?.latest?.buckets.map((bucket) => - createResultFromDocument(bucket.latest_doc.hits.hits[0]._source) - ) ?? []; + const results: ResultDocument[] = + aggregations?.latest?.buckets.map((bucket) => bucket.latest_doc.hits.hits[0]._source) ?? + []; return response.ok({ body: results }); } catch (err) { - logger.error(JSON.stringify(err)); + logger.error(err.message); return resp.error({ body: err.message ?? API_DEFAULT_ERROR_MESSAGE, diff --git a/x-pack/plugins/ecs_data_quality_dashboard/server/routes/results/parser.test.ts b/x-pack/plugins/ecs_data_quality_dashboard/server/routes/results/parser.test.ts deleted file mode 100644 index 56800801ffc8f..0000000000000 --- a/x-pack/plugins/ecs_data_quality_dashboard/server/routes/results/parser.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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { createDocumentFromResult, createResultFromDocument } from './parser'; -import { resultBody, resultDocument } from './results.mock'; - -describe('createDocumentFromResult', () => { - it('should create document from result', () => { - const document = createDocumentFromResult(resultBody); - expect(document).toEqual({ ...resultDocument, '@timestamp': expect.any(Number) }); - }); -}); - -describe('createResultFromDocument', () => { - it('should create document from result', () => { - const result = createResultFromDocument(resultDocument); - expect(result).toEqual({ ...resultBody, '@timestamp': expect.any(Number) }); - }); -}); diff --git a/x-pack/plugins/ecs_data_quality_dashboard/server/routes/results/parser.ts b/x-pack/plugins/ecs_data_quality_dashboard/server/routes/results/parser.ts deleted file mode 100644 index 198d5522839e4..0000000000000 --- a/x-pack/plugins/ecs_data_quality_dashboard/server/routes/results/parser.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 type { Result, ResultDocument, IndexArray, IndexObject } from '../../schemas/result'; - -export const createDocumentFromResult = (result: Result): ResultDocument => { - const { rollup } = result; - const document: ResultDocument = { - ...result, - '@timestamp': Date.now(), - rollup: { - ...rollup, - ilmExplain: indexObjectToIndexArray(rollup.ilmExplain), - stats: indexObjectToIndexArray(rollup.stats), - results: indexObjectToIndexArray(rollup.results), - }, - }; - - return document; -}; - -export const createResultFromDocument = (document: ResultDocument): Result => { - const { rollup } = document; - const result = { - ...document, - rollup: { - ...rollup, - ilmExplain: indexArrayToIndexObject(rollup.ilmExplain), - stats: indexArrayToIndexObject(rollup.stats), - results: indexArrayToIndexObject(rollup.results), - }, - }; - - return result; -}; - -// ES parses object keys containing `.` as nested dot-separated field names (e.g. `event.name`). -// we need to convert documents containing objects with "indexName" keys (e.g. `.index-name-checked`) -// to object arrays so they can be stored correctly, we keep the key in the `_indexName` field. -const indexObjectToIndexArray = (obj: IndexObject): IndexArray => - Object.entries(obj).map(([key, value]) => ({ ...value, _indexName: key })); - -// convert index arrays back to objects with indexName as key -const indexArrayToIndexObject = (arr: IndexArray): IndexObject => - Object.fromEntries(arr.map(({ _indexName, ...value }) => [_indexName, value])); diff --git a/x-pack/plugins/ecs_data_quality_dashboard/server/routes/results/post_results.test.ts b/x-pack/plugins/ecs_data_quality_dashboard/server/routes/results/post_results.test.ts index 98eb67ecbaaa8..f3175a737ee54 100644 --- a/x-pack/plugins/ecs_data_quality_dashboard/server/routes/results/post_results.test.ts +++ b/x-pack/plugins/ecs_data_quality_dashboard/server/routes/results/post_results.test.ts @@ -11,20 +11,29 @@ import { requestMock } from '../../__mocks__/request'; import { requestContextMock } from '../../__mocks__/request_context'; import { postResultsRoute } from './post_results'; import { loggerMock, type MockedLogger } from '@kbn/logging-mocks'; -import type { - SecurityHasPrivilegesResponse, - WriteResponseBase, -} from '@elastic/elasticsearch/lib/api/types'; -import { resultBody, resultDocument } from './results.mock'; - -// TODO: https://github.com/elastic/kibana/pull/173185#issuecomment-1908034302 -describe.skip('postResultsRoute route', () => { +import type { WriteResponseBase } from '@elastic/elasticsearch/lib/api/types'; +import { resultDocument } from './results.mock'; +import type { CheckIndicesPrivilegesParam } from './privileges'; + +const mockCheckIndicesPrivileges = jest.fn(({ indices }: CheckIndicesPrivilegesParam) => + Promise.resolve(Object.fromEntries(indices.map((index) => [index, true]))) +); +jest.mock('./privileges', () => ({ + checkIndicesPrivileges: (params: CheckIndicesPrivilegesParam) => + mockCheckIndicesPrivileges(params), +})); + +describe('postResultsRoute route', () => { describe('indexation', () => { let server: ReturnType; let { context } = requestContextMock.createTools(); let logger: MockedLogger; - const req = requestMock.create({ method: 'post', path: RESULTS_ROUTE_PATH, body: resultBody }); + const req = requestMock.create({ + method: 'post', + path: RESULTS_ROUTE_PATH, + body: resultDocument, + }); beforeEach(() => { jest.clearAllMocks(); @@ -34,10 +43,9 @@ describe.skip('postResultsRoute route', () => { ({ context } = requestContextMock.createTools()); - context.core.elasticsearch.client.asCurrentUser.security.hasPrivileges.mockResolvedValue({ - has_all_requested: true, - } as unknown as SecurityHasPrivilegesResponse); - + context.core.elasticsearch.client.asInternalUser.indices.get.mockResolvedValue({ + [resultDocument.indexName]: {}, + }); postResultsRoute(server.router, logger); }); @@ -80,12 +88,16 @@ describe.skip('postResultsRoute route', () => { }); }); - describe('request pattern authorization', () => { + describe('request index authorization', () => { let server: ReturnType; let { context } = requestContextMock.createTools(); let logger: MockedLogger; - const req = requestMock.create({ method: 'post', path: RESULTS_ROUTE_PATH, body: resultBody }); + const req = requestMock.create({ + method: 'post', + path: RESULTS_ROUTE_PATH, + body: resultDocument, + }); beforeEach(() => { jest.clearAllMocks(); @@ -95,6 +107,9 @@ describe.skip('postResultsRoute route', () => { ({ context } = requestContextMock.createTools()); + context.core.elasticsearch.client.asInternalUser.indices.get.mockResolvedValue({ + [resultDocument.indexName]: {}, + }); context.core.elasticsearch.client.asInternalUser.index.mockResolvedValueOnce({ result: 'created', } as WriteResponseBase); @@ -102,42 +117,41 @@ describe.skip('postResultsRoute route', () => { postResultsRoute(server.router, logger); }); - it('should authorize pattern', async () => { - const mockHasPrivileges = - context.core.elasticsearch.client.asCurrentUser.security.hasPrivileges; - mockHasPrivileges.mockResolvedValueOnce({ - has_all_requested: true, - } as unknown as SecurityHasPrivilegesResponse); + it('should authorize index', async () => { + const response = await server.inject(req, requestContextMock.convertContext(context)); + expect(mockCheckIndicesPrivileges).toHaveBeenCalledWith({ + client: context.core.elasticsearch.client, + indices: [resultDocument.indexName], + }); + expect(context.core.elasticsearch.client.asInternalUser.index).toHaveBeenCalled(); + expect(response.status).toEqual(200); + expect(response.body).toEqual({ result: 'created' }); + }); + + it('should authorize data stream', async () => { + const dataStreamName = 'test_data_stream_name'; + context.core.elasticsearch.client.asInternalUser.indices.get.mockResolvedValue({ + [resultDocument.indexName]: { data_stream: dataStreamName }, + }); + mockCheckIndicesPrivileges.mockResolvedValueOnce({ [dataStreamName]: true }); const response = await server.inject(req, requestContextMock.convertContext(context)); - expect(mockHasPrivileges).toHaveBeenCalledWith({ - index: [ - { - names: [resultBody.rollup.pattern], - privileges: ['all', 'read', 'view_index_metadata'], - }, - ], + expect(mockCheckIndicesPrivileges).toHaveBeenCalledWith({ + client: context.core.elasticsearch.client, + indices: [dataStreamName], }); expect(context.core.elasticsearch.client.asInternalUser.index).toHaveBeenCalled(); expect(response.status).toEqual(200); expect(response.body).toEqual({ result: 'created' }); }); - it('should not index unauthorized pattern', async () => { - const mockHasPrivileges = - context.core.elasticsearch.client.asCurrentUser.security.hasPrivileges; - mockHasPrivileges.mockResolvedValueOnce({ - has_all_requested: false, - } as unknown as SecurityHasPrivilegesResponse); + it('should not index unauthorized index', async () => { + mockCheckIndicesPrivileges.mockResolvedValueOnce({ [resultDocument.indexName]: false }); const response = await server.inject(req, requestContextMock.convertContext(context)); - expect(mockHasPrivileges).toHaveBeenCalledWith({ - index: [ - { - names: [resultBody.rollup.pattern], - privileges: ['all', 'read', 'view_index_metadata'], - }, - ], + expect(mockCheckIndicesPrivileges).toHaveBeenCalledWith({ + client: context.core.elasticsearch.client, + indices: [resultDocument.indexName], }); expect(context.core.elasticsearch.client.asInternalUser.index).not.toHaveBeenCalled(); @@ -145,11 +159,9 @@ describe.skip('postResultsRoute route', () => { expect(response.body).toEqual({ result: 'noop' }); }); - it('handles pattern authorization error', async () => { + it('handles index authorization error', async () => { const errorMessage = 'Error!'; - const mockHasPrivileges = - context.core.elasticsearch.client.asCurrentUser.security.hasPrivileges; - mockHasPrivileges.mockRejectedValueOnce({ message: errorMessage }); + mockCheckIndicesPrivileges.mockRejectedValueOnce(Error(errorMessage)); const response = await server.inject(req, requestContextMock.convertContext(context)); expect(response.status).toEqual(500); @@ -170,7 +182,7 @@ describe.skip('postResultsRoute route', () => { const req = requestMock.create({ method: 'post', path: RESULTS_ROUTE_PATH, - body: { rollup: resultBody.rollup }, + body: { indexName: 'invalid body' }, }); const result = server.validate(req); diff --git a/x-pack/plugins/ecs_data_quality_dashboard/server/routes/results/post_results.ts b/x-pack/plugins/ecs_data_quality_dashboard/server/routes/results/post_results.ts index 1162d23f1dfad..b4b2e4b219bc4 100644 --- a/x-pack/plugins/ecs_data_quality_dashboard/server/routes/results/post_results.ts +++ b/x-pack/plugins/ecs_data_quality_dashboard/server/routes/results/post_results.ts @@ -13,7 +13,7 @@ import { buildRouteValidation } from '../../schemas/common'; import { PostResultBody } from '../../schemas/result'; import { API_DEFAULT_ERROR_MESSAGE } from '../../translations'; import type { DataQualityDashboardRequestHandlerContext } from '../../types'; -import { createDocumentFromResult } from './parser'; +import { checkIndicesPrivileges } from './privileges'; import { API_RESULTS_INDEX_NOT_AVAILABLE } from './translations'; export const postResultsRoute = ( @@ -32,10 +32,6 @@ export const postResultsRoute = ( validate: { request: { body: buildRouteValidation(PostResultBody) } }, }, async (context, request, response) => { - // TODO: https://github.com/elastic/kibana/pull/173185#issuecomment-1908034302 - return response.ok({ body: { result: 'noop' } }); - - // eslint-disable-next-line no-unreachable const services = await context.resolve(['core', 'dataQualityDashboard']); const resp = buildResponse(response); @@ -51,24 +47,35 @@ export const postResultsRoute = ( } try { - // Confirm user has authorization for the pattern payload - const { pattern } = request.body.rollup; - const userEsClient = services.core.elasticsearch.client.asCurrentUser; - const privileges = await userEsClient.security.hasPrivileges({ - index: [{ names: [pattern], privileges: ['all', 'read', 'view_index_metadata'] }], + const { client } = services.core.elasticsearch; + const { indexName } = request.body; + + // Confirm index exists and get the data stream name if it's a data stream + const indicesResponse = await client.asInternalUser.indices.get({ + index: indexName, + features: 'aliases', + }); + if (!indicesResponse[indexName]) { + return response.ok({ body: { result: 'noop' } }); + } + const indexOrDataStream = indicesResponse[indexName].data_stream ?? indexName; + + // Confirm user has authorization for the index name or data stream + const hasIndexPrivileges = await checkIndicesPrivileges({ + client, + indices: [indexOrDataStream], }); - if (!privileges.has_all_requested) { + if (!hasIndexPrivileges[indexOrDataStream]) { return response.ok({ body: { result: 'noop' } }); } // Index the result - const document = createDocumentFromResult(request.body); - const esClient = services.core.elasticsearch.client.asInternalUser; - const outcome = await esClient.index({ index, body: document }); + const body = { '@timestamp': Date.now(), ...request.body }; + const outcome = await client.asInternalUser.index({ index, body }); return response.ok({ body: { result: outcome.result } }); } catch (err) { - logger.error(JSON.stringify(err)); + logger.error(err.message); return resp.error({ body: err.message ?? API_DEFAULT_ERROR_MESSAGE, diff --git a/x-pack/plugins/ecs_data_quality_dashboard/server/routes/results/privileges.test.ts b/x-pack/plugins/ecs_data_quality_dashboard/server/routes/results/privileges.test.ts new file mode 100644 index 0000000000000..2833e3f030fdd --- /dev/null +++ b/x-pack/plugins/ecs_data_quality_dashboard/server/routes/results/privileges.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 type { SecurityHasPrivilegesResponse } from '@elastic/elasticsearch/lib/api/types'; +import { requestContextMock } from '../../__mocks__/request_context'; +import { checkIndicesPrivileges } from './privileges'; + +// const mockHasPrivileges = +// context.core.elasticsearch.client.asCurrentUser.security.hasPrivileges; +// mockHasPrivileges.mockResolvedValueOnce({ +// has_all_requested: true, +// } as unknown as SecurityHasPrivilegesResponse); + +describe('checkIndicesPrivileges', () => { + const { context } = requestContextMock.createTools(); + const { client } = context.core.elasticsearch; + + beforeEach(() => { + client.asCurrentUser.security.hasPrivileges.mockResolvedValue({ + index: { + index1: { + read: true, + view_index_metadata: true, + manage: true, + monitor: true, + }, + index2: { + read: true, + view_index_metadata: true, + manage: true, + monitor: true, + }, + }, + } as unknown as SecurityHasPrivilegesResponse); + }); + + it('should return true if user has required privileges', async () => { + const result = await checkIndicesPrivileges({ client, indices: ['index1', 'index2'] }); + expect(result).toEqual({ index1: true, index2: true }); + }); + + it('should return true if only monitor privileges is missing', async () => { + client.asCurrentUser.security.hasPrivileges.mockResolvedValueOnce({ + index: { + index1: { + read: true, + view_index_metadata: true, + manage: true, + monitor: false, + }, + }, + } as unknown as SecurityHasPrivilegesResponse); + const result = await checkIndicesPrivileges({ client, indices: ['index1'] }); + + expect(result).toEqual({ index1: true }); + }); + + it('should return true if only manage privileges is missing', async () => { + client.asCurrentUser.security.hasPrivileges.mockResolvedValueOnce({ + index: { + index1: { + read: true, + view_index_metadata: true, + manage: false, + monitor: true, + }, + }, + } as unknown as SecurityHasPrivilegesResponse); + + const result = await checkIndicesPrivileges({ client, indices: ['index1'] }); + + expect(result).toEqual({ index1: true }); + }); + + it('should return false if both manage and monitor privileges is missing', async () => { + client.asCurrentUser.security.hasPrivileges.mockResolvedValueOnce({ + index: { + index1: { + read: true, + view_index_metadata: true, + manage: false, + monitor: false, + }, + }, + } as unknown as SecurityHasPrivilegesResponse); + + const result = await checkIndicesPrivileges({ client, indices: ['index1'] }); + + expect(result).toEqual({ index1: false }); + }); + + it('should return false if only read privilege is missing', async () => { + client.asCurrentUser.security.hasPrivileges.mockResolvedValueOnce({ + index: { + index1: { + read: false, + view_index_metadata: true, + manage: true, + monitor: true, + }, + }, + } as unknown as SecurityHasPrivilegesResponse); + + const result = await checkIndicesPrivileges({ client, indices: ['index1'] }); + + expect(result).toEqual({ index1: false }); + }); + + it('should return false if only view_index_metadata privilege is missing', async () => { + client.asCurrentUser.security.hasPrivileges.mockResolvedValueOnce({ + index: { + index1: { + read: true, + view_index_metadata: false, + manage: true, + monitor: true, + }, + }, + } as unknown as SecurityHasPrivilegesResponse); + + const result = await checkIndicesPrivileges({ client, indices: ['index1'] }); + + expect(result).toEqual({ index1: false }); + }); +}); diff --git a/x-pack/plugins/ecs_data_quality_dashboard/server/routes/results/privileges.ts b/x-pack/plugins/ecs_data_quality_dashboard/server/routes/results/privileges.ts new file mode 100644 index 0000000000000..ebda2f54e16e0 --- /dev/null +++ b/x-pack/plugins/ecs_data_quality_dashboard/server/routes/results/privileges.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 { IScopedClusterClient } from '@kbn/core-elasticsearch-server'; + +export interface CheckIndicesPrivilegesParam { + client: IScopedClusterClient; + indices: string[]; +} + +/** + * Checks user has the required privileges to do a results check for the given indices. + * In order to be allowed to do a result check user needs: + * `read`, `view_index_metadata` and (`monitor` or `manage`) index privileges. + */ +export const checkIndicesPrivileges = async ({ client, indices }: CheckIndicesPrivilegesParam) => { + const privileges = await client.asCurrentUser.security.hasPrivileges({ + index: [{ names: indices, privileges: ['read', 'view_index_metadata', 'monitor', 'manage'] }], + }); + + const hasRequiredIndexPrivilege: Record = {}; + Object.entries(privileges.index).forEach( + ([indexName, { read, view_index_metadata: viewMetadata, monitor, manage }]) => { + hasRequiredIndexPrivilege[indexName] = read && viewMetadata && (monitor || manage); + } + ); + + return hasRequiredIndexPrivilege; +}; diff --git a/x-pack/plugins/ecs_data_quality_dashboard/server/routes/results/results.mock.ts b/x-pack/plugins/ecs_data_quality_dashboard/server/routes/results/results.mock.ts index 1d0b15a4c24c0..36ca3d2dc4e66 100644 --- a/x-pack/plugins/ecs_data_quality_dashboard/server/routes/results/results.mock.ts +++ b/x-pack/plugins/ecs_data_quality_dashboard/server/routes/results/results.mock.ts @@ -8,195 +8,29 @@ import type { ResultDocument } from '../../schemas/result'; export const resultDocument: ResultDocument = { - '@timestamp': 1622767273955, - meta: { - batchId: 'aae36cd8-3825-4ad1-baa4-79bdf4617f8a', - ecsVersion: '8.6.1', - errorCount: 0, - ilmPhase: 'hot', - indexId: 'aO29KOwtQ3Snf-Pit5Wf4w', - indexName: '.internal.alerts-security.alerts-default-000001', - isCheckAll: true, - numberOfDocuments: 20, - numberOfFields: 1726, - numberOfIncompatibleFields: 2, - numberOfEcsFields: 1440, - numberOfCustomFields: 284, - numberOfIndices: 1, - numberOfIndicesChecked: 1, - numberOfSameFamily: 0, - sameFamilyFields: [], - sizeInBytes: 506471, - timeConsumedMs: 85, - unallowedMappingFields: [], - unallowedValueFields: ['event.category', 'event.outcome'], - }, - rollup: { - docsCount: 20, - error: null, - ilmExplain: [ - { - _indexName: '.internal.alerts-security.alerts-default-000001', - index: '.internal.alerts-security.alerts-default-000001', - managed: true, - policy: '.alerts-ilm-policy', - index_creation_date_millis: 1700757268526, - time_since_index_creation: '20.99d', - lifecycle_date_millis: 1700757268526, - age: '20.99d', - phase: 'hot', - phase_time_millis: 1700757270294, - action: 'rollover', - action_time_millis: 1700757273955, - step: 'check-rollover-ready', - step_time_millis: 1700757273955, - phase_execution: { - policy: '.alerts-ilm-policy', - phase_definition: { - min_age: '0ms', - actions: { - rollover: { - max_age: '30d', - max_primary_shard_size: '50gb', - }, - }, - }, - version: 1, - modified_date_in_millis: 1700757266723, - }, - }, - ], - ilmExplainPhaseCounts: { - hot: 1, - warm: 0, - cold: 0, - frozen: 0, - unmanaged: 0, - }, - indices: 1, - pattern: '.alerts-security.alerts-default', - results: [ - { - _indexName: '.internal.alerts-security.alerts-default-000001', - docsCount: 20, - error: null, - ilmPhase: 'hot', - incompatible: 2, - indexName: '.internal.alerts-security.alerts-default-000001', - markdownComments: [ - '### .internal.alerts-security.alerts-default-000001\n', - '| Result | Index | Docs | Incompatible fields | ILM Phase | Size |\n|--------|-------|------|---------------------|-----------|------|\n| ❌ | .internal.alerts-security.alerts-default-000001 | 20 (100,0 %) | 2 | `hot` | 494.6KB |\n\n', - '### **Incompatible fields** `2` **Same family** `0` **Custom fields** `284` **ECS compliant fields** `1440` **All fields** `1726`\n', - "#### 2 incompatible fields\n\nFields are incompatible with ECS when index mappings, or the values of the fields in the index, don't conform to the Elastic Common Schema (ECS), version 8.6.1.\n\n❌ Detection engine rules referencing these fields may not match them correctly\n❌ Pages may not display some events or fields due to unexpected field mappings or values\n❌ Mappings or field values that don't comply with ECS are not supported\n", - '\n\n#### Incompatible field values - .internal.alerts-security.alerts-default-000001\n\n\n| Field | ECS values (expected) | Document values (actual) | \n|-------|-----------------------|--------------------------|\n| event.category | `authentication`, `configuration`, `database`, `driver`, `email`, `file`, `host`, `iam`, `intrusion_detection`, `malware`, `network`, `package`, `process`, `registry`, `session`, `threat`, `vulnerability`, `web` | `behavior` (1) |\n| event.outcome | `failure`, `success`, `unknown` | `` (12) |\n\n', - ], - pattern: '.alerts-security.alerts-default', - sameFamily: 0, - }, - ], - sizeInBytes: 506471, - stats: [ - { - _indexName: '.internal.alerts-security.alerts-default-000001', - uuid: 'aO29KOwtQ3Snf-Pit5Wf4w', - health: 'green', - status: 'open', - }, - ], - }, -}; - -export const resultBody = { - meta: { - batchId: 'aae36cd8-3825-4ad1-baa4-79bdf4617f8a', - ecsVersion: '8.6.1', - errorCount: 0, - ilmPhase: 'hot', - indexId: 'aO29KOwtQ3Snf-Pit5Wf4w', - indexName: '.internal.alerts-security.alerts-default-000001', - isCheckAll: true, - numberOfDocuments: 20, - numberOfFields: 1726, - numberOfIncompatibleFields: 2, - numberOfEcsFields: 1440, - numberOfCustomFields: 284, - numberOfIndices: 1, - numberOfIndicesChecked: 1, - numberOfSameFamily: 0, - sameFamilyFields: [], - sizeInBytes: 506471, - timeConsumedMs: 85, - unallowedMappingFields: [], - unallowedValueFields: ['event.category', 'event.outcome'], - }, - rollup: { - docsCount: 20, - error: null, - ilmExplain: { - '.internal.alerts-security.alerts-default-000001': { - index: '.internal.alerts-security.alerts-default-000001', - managed: true, - policy: '.alerts-ilm-policy', - index_creation_date_millis: 1700757268526, - time_since_index_creation: '20.99d', - lifecycle_date_millis: 1700757268526, - age: '20.99d', - phase: 'hot', - phase_time_millis: 1700757270294, - action: 'rollover', - action_time_millis: 1700757273955, - step: 'check-rollover-ready', - step_time_millis: 1700757273955, - phase_execution: { - policy: '.alerts-ilm-policy', - phase_definition: { - min_age: '0ms', - actions: { - rollover: { - max_age: '30d', - max_primary_shard_size: '50gb', - }, - }, - }, - version: 1, - modified_date_in_millis: 1700757266723, - }, - }, - }, - ilmExplainPhaseCounts: { - hot: 1, - warm: 0, - cold: 0, - frozen: 0, - unmanaged: 0, - }, - indices: 1, - pattern: '.alerts-security.alerts-default', - results: { - '.internal.alerts-security.alerts-default-000001': { - docsCount: 20, - error: null, - ilmPhase: 'hot', - incompatible: 2, - indexName: '.internal.alerts-security.alerts-default-000001', - markdownComments: [ - '### .internal.alerts-security.alerts-default-000001\n', - '| Result | Index | Docs | Incompatible fields | ILM Phase | Size |\n|--------|-------|------|---------------------|-----------|------|\n| ❌ | .internal.alerts-security.alerts-default-000001 | 20 (100,0 %) | 2 | `hot` | 494.6KB |\n\n', - '### **Incompatible fields** `2` **Same family** `0` **Custom fields** `284` **ECS compliant fields** `1440` **All fields** `1726`\n', - "#### 2 incompatible fields\n\nFields are incompatible with ECS when index mappings, or the values of the fields in the index, don't conform to the Elastic Common Schema (ECS), version 8.6.1.\n\n❌ Detection engine rules referencing these fields may not match them correctly\n❌ Pages may not display some events or fields due to unexpected field mappings or values\n❌ Mappings or field values that don't comply with ECS are not supported\n", - '\n\n#### Incompatible field values - .internal.alerts-security.alerts-default-000001\n\n\n| Field | ECS values (expected) | Document values (actual) | \n|-------|-----------------------|--------------------------|\n| event.category | `authentication`, `configuration`, `database`, `driver`, `email`, `file`, `host`, `iam`, `intrusion_detection`, `malware`, `network`, `package`, `process`, `registry`, `session`, `threat`, `vulnerability`, `web` | `behavior` (1) |\n| event.outcome | `failure`, `success`, `unknown` | `` (12) |\n\n', - ], - pattern: '.alerts-security.alerts-default', - sameFamily: 0, - }, - }, - sizeInBytes: 506471, - stats: { - '.internal.alerts-security.alerts-default-000001': { - uuid: 'aO29KOwtQ3Snf-Pit5Wf4w', - health: 'green', - status: 'open', - }, - }, - }, + batchId: '33d95427-1fd3-43c3-bdeb-74324533a31e', + indexName: '.ds-logs-endpoint.alerts-default-2023.11.23-000001', + isCheckAll: false, + checkedAt: 1706526408000, + docsCount: 100, + totalFieldCount: 1582, + ecsFieldCount: 677, + customFieldCount: 904, + incompatibleFieldCount: 1, + sameFamilyFieldCount: 0, + sameFamilyFields: [], + unallowedMappingFields: [], + unallowedValueFields: ['event.category'], + sizeInBytes: 173796, + ilmPhase: 'hot', + markdownComments: [ + '### .ds-logs-endpoint.alerts-default-2023.11.23-000001\n', + '| Result | Index | Docs | Incompatible fields | ILM Phase | Size |\n|--------|-------|------|---------------------|-----------|------|\n| ❌ | .ds-logs-endpoint.alerts-default-2023.11.23-000001 | 100 (64,1 %) | 1 | `hot` | 274.6KB |\n\n', + '### **Incompatible fields** `1` **Same family** `0` **Custom fields** `904` **ECS compliant fields** `677` **All fields** `1582`\n', + "#### 1 incompatible field\n\nFields are incompatible with ECS when index mappings, or the values of the fields in the index, don't conform to the Elastic Common Schema (ECS), version 8.6.1.\n\n❌ Detection engine rules referencing these fields may not match them correctly\n❌ Pages may not display some events or fields due to unexpected field mappings or values\n❌ Mappings or field values that don't comply with ECS are not supported\n", + '\n\n#### Incompatible field values - .ds-logs-endpoint.alerts-default-2023.11.23-000001\n\n\n| Field | ECS values (expected) | Document values (actual) | \n|-------|-----------------------|--------------------------|\n| event.category | `authentication`, `configuration`, `database`, `driver`, `email`, `file`, `host`, `iam`, `intrusion_detection`, `malware`, `network`, `package`, `process`, `registry`, `session`, `threat`, `vulnerability`, `web` | `behavior` (6) |\n\n', + ], + ecsVersion: '8.6.1', + indexId: 'PMhntcuPQ_yhPoNsXiM_hg', + error: null, }; diff --git a/x-pack/plugins/ecs_data_quality_dashboard/server/schemas/result.ts b/x-pack/plugins/ecs_data_quality_dashboard/server/schemas/result.ts index 09851c9b8dc86..69387ea6fd8cf 100644 --- a/x-pack/plugins/ecs_data_quality_dashboard/server/schemas/result.ts +++ b/x-pack/plugins/ecs_data_quality_dashboard/server/schemas/result.ts @@ -7,64 +7,30 @@ import * as t from 'io-ts'; -export const ResultMeta = t.type({ +export const ResultDocument = t.type({ batchId: t.string, - ecsVersion: t.string, - errorCount: t.number, - ilmPhase: t.string, - indexId: t.string, indexName: t.string, isCheckAll: t.boolean, - numberOfDocuments: t.number, - numberOfFields: t.number, - numberOfIncompatibleFields: t.number, - numberOfEcsFields: t.number, - numberOfCustomFields: t.number, - numberOfIndices: t.number, - numberOfIndicesChecked: t.number, - numberOfSameFamily: t.number, + checkedAt: t.number, + docsCount: t.number, + totalFieldCount: t.number, + ecsFieldCount: t.number, + customFieldCount: t.number, + incompatibleFieldCount: t.number, + sameFamilyFieldCount: t.number, sameFamilyFields: t.array(t.string), - sizeInBytes: t.number, - timeConsumedMs: t.number, unallowedMappingFields: t.array(t.string), unallowedValueFields: t.array(t.string), -}); -export type ResultMeta = t.TypeOf; - -export const ResultRollup = t.type({ - docsCount: t.number, - error: t.union([t.string, t.null]), - indices: t.number, - pattern: t.string, sizeInBytes: t.number, - ilmExplainPhaseCounts: t.record(t.string, t.number), - ilmExplain: t.record(t.string, t.UnknownRecord), - stats: t.record(t.string, t.UnknownRecord), - results: t.record(t.string, t.UnknownRecord), -}); -export type ResultRollup = t.TypeOf; - -export const Result = t.type({ - meta: ResultMeta, - rollup: ResultRollup, + ilmPhase: t.string, + markdownComments: t.array(t.string), + ecsVersion: t.string, + indexId: t.string, + error: t.union([t.string, t.null]), }); -export type Result = t.TypeOf; - -export type IndexArray = Array<{ _indexName: string } & Record>; -export type IndexObject = Record>; +export type ResultDocument = t.TypeOf; -export type ResultDocument = Omit & { - '@timestamp': number; - rollup: Omit & { - stats: IndexArray; - results: IndexArray; - ilmExplain: IndexArray; - }; -}; +export const PostResultBody = ResultDocument; -// Routes validation schemas - -export const GetResultQuery = t.type({ patterns: t.string }); +export const GetResultQuery = t.type({ pattern: t.string }); export type GetResultQuery = t.TypeOf; - -export const PostResultBody = Result; diff --git a/x-pack/plugins/ecs_data_quality_dashboard/tsconfig.json b/x-pack/plugins/ecs_data_quality_dashboard/tsconfig.json index 04a7d2bf092f5..b725beec802b2 100644 --- a/x-pack/plugins/ecs_data_quality_dashboard/tsconfig.json +++ b/x-pack/plugins/ecs_data_quality_dashboard/tsconfig.json @@ -24,6 +24,7 @@ "@kbn/data-stream-adapter", "@kbn/spaces-plugin", "@kbn/core-elasticsearch-server-mocks", + "@kbn/core-elasticsearch-server", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/fetch_connector_by_id_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/fetch_connector_by_id_logic.ts new file mode 100644 index 0000000000000..320760e49ee72 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/fetch_connector_by_id_logic.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 { Connector } from '@kbn/search-connectors'; + +import { createApiLogic, Actions } from '../../../shared/api_logic/create_api_logic'; +import { HttpLogic } from '../../../shared/http'; + +export interface FetchConnectorByIdApiLogicArgs { + connectorId: string; +} +export interface FetchConnectorByIdApiLogicResponse { + connector: Connector | undefined; +} + +export const fetchConnectorById = async ({ + connectorId, +}: FetchConnectorByIdApiLogicArgs): Promise => { + const route = `/internal/enterprise_search/connectors/${connectorId}`; + const response = await HttpLogic.values.http.get(route); + return response; +}; + +export const FetchConnectorByIdApiLogic = createApiLogic( + ['fetch_connector_by_id_api_logic'], + fetchConnectorById +); + +export type FetchConnectorByIdApiLogicActions = Actions< + FetchConnectorByIdApiLogicArgs, + FetchConnectorByIdApiLogicResponse +>; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/connector_detail.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/connector_detail.tsx new file mode 100644 index 0000000000000..a3f77406750e9 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/connector_detail.tsx @@ -0,0 +1,236 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useEffect } from 'react'; +import { useParams } from 'react-router-dom'; + +import { useActions, useValues } from 'kea'; + +import { i18n } from '@kbn/i18n'; + +import { generateEncodedPath } from '../../../shared/encode_path_params'; +import { KibanaLogic } from '../../../shared/kibana'; +import { CONNECTOR_DETAIL_TAB_PATH } from '../../routes'; +import { baseBreadcrumbs } from '../connectors/connectors'; +import { EnterpriseSearchContentPageTemplate } from '../layout/page_template'; + +import { getHeaderActions } from '../search_index/components/header_actions/header_actions'; +import { ConnectorConfiguration } from '../search_index/connector/connector_configuration'; +import { ConnectorSchedulingComponent } from '../search_index/connector/connector_scheduling'; +import { ConnectorSyncRules } from '../search_index/connector/sync_rules/connector_rules'; +import { SearchIndexDocuments } from '../search_index/documents'; +import { SearchIndexIndexMappings } from '../search_index/index_mappings'; +import { SearchIndexPipelines } from '../search_index/pipelines/pipelines'; + +import { ConnectorViewLogic } from './connector_view_logic'; +import { ConnectorDetailOverview } from './overview'; + +export enum ConnectorDetailTabId { + // all indices + OVERVIEW = 'overview', + DOCUMENTS = 'documents', + INDEX_MAPPINGS = 'index_mappings', + PIPELINES = 'pipelines', + // connector indices + CONFIGURATION = 'configuration', + SYNC_RULES = 'sync_rules', + SCHEDULING = 'scheduling', +} + +export const ConnectorDetail: React.FC = () => { + const connectorId = decodeURIComponent(useParams<{ connectorId: string }>().connectorId); + const { hasFilteringFeature, isLoading, index, connector } = useValues(ConnectorViewLogic); + const { fetchConnector } = useActions(ConnectorViewLogic); + useEffect(() => { + fetchConnector({ connectorId }); + }, []); + + const { tabId = ConnectorDetailTabId.OVERVIEW } = useParams<{ + tabId?: string; + }>(); + + const { + productAccess: { hasAppSearchAccess }, + productFeatures: { hasDefaultIngestPipeline }, + } = useValues(KibanaLogic); + + const ALL_INDICES_TABS = [ + { + content: , + id: ConnectorDetailTabId.OVERVIEW, + isSelected: tabId === ConnectorDetailTabId.OVERVIEW, + label: i18n.translate( + 'xpack.enterpriseSearch.content.connectors.connectorDetail.overviewTabLabel', + { + defaultMessage: 'Overview', + } + ), + onClick: () => + KibanaLogic.values.navigateToUrl( + generateEncodedPath(CONNECTOR_DETAIL_TAB_PATH, { + connectorId, + tabId: ConnectorDetailTabId.OVERVIEW, + }) + ), + }, + { + content: , + disabled: !index, + id: ConnectorDetailTabId.DOCUMENTS, + isSelected: tabId === ConnectorDetailTabId.DOCUMENTS, + label: i18n.translate( + 'xpack.enterpriseSearch.content.connectors.connectorDetail.documentsTabLabel', + { + defaultMessage: 'Documents', + } + ), + onClick: () => + KibanaLogic.values.navigateToUrl( + generateEncodedPath(CONNECTOR_DETAIL_TAB_PATH, { + connectorId, + tabId: ConnectorDetailTabId.DOCUMENTS, + }) + ), + }, + { + content: , + disabled: !index, + id: ConnectorDetailTabId.INDEX_MAPPINGS, + isSelected: tabId === ConnectorDetailTabId.INDEX_MAPPINGS, + label: i18n.translate( + 'xpack.enterpriseSearch.content.connectors.connectorDetail.indexMappingsTabLabel', + { + defaultMessage: 'Index mappings', + } + ), + onClick: () => + KibanaLogic.values.navigateToUrl( + generateEncodedPath(CONNECTOR_DETAIL_TAB_PATH, { + connectorId, + tabId: ConnectorDetailTabId.INDEX_MAPPINGS, + }) + ), + }, + ]; + + const CONNECTOR_TABS = [ + { + content: , + id: ConnectorDetailTabId.CONFIGURATION, + isSelected: tabId === ConnectorDetailTabId.CONFIGURATION, + label: i18n.translate( + 'xpack.enterpriseSearch.content.connectors.connectorDetail.configurationTabLabel', + { + defaultMessage: 'Configuration', + } + ), + onClick: () => + KibanaLogic.values.navigateToUrl( + generateEncodedPath(CONNECTOR_DETAIL_TAB_PATH, { + connectorId, + tabId: ConnectorDetailTabId.CONFIGURATION, + }) + ), + }, + ...(hasFilteringFeature + ? [ + { + content: , + disabled: !index, + id: ConnectorDetailTabId.SYNC_RULES, + isSelected: tabId === ConnectorDetailTabId.SYNC_RULES, + label: i18n.translate( + 'xpack.enterpriseSearch.content.connectors.connectorDetail.syncRulesTabLabel', + { + defaultMessage: 'Sync rules', + } + ), + onClick: () => + KibanaLogic.values.navigateToUrl( + generateEncodedPath(CONNECTOR_DETAIL_TAB_PATH, { + connectorId, + tabId: ConnectorDetailTabId.SYNC_RULES, + }) + ), + }, + ] + : []), + { + content: , + disabled: !index, + id: ConnectorDetailTabId.SCHEDULING, + isSelected: tabId === ConnectorDetailTabId.SCHEDULING, + label: i18n.translate( + 'xpack.enterpriseSearch.content.connectors.connectorDetail.schedulingTabLabel', + { + defaultMessage: 'Scheduling', + } + ), + onClick: () => + KibanaLogic.values.navigateToUrl( + generateEncodedPath(CONNECTOR_DETAIL_TAB_PATH, { + connectorId, + tabId: ConnectorDetailTabId.SCHEDULING, + }) + ), + }, + ]; + + const PIPELINES_TAB = { + content: , + disabled: !index, + id: ConnectorDetailTabId.PIPELINES, + isSelected: tabId === ConnectorDetailTabId.PIPELINES, + label: i18n.translate( + 'xpack.enterpriseSearch.content.connectors.connectorDetail.pipelinesTabLabel', + { + defaultMessage: 'Pipelines', + } + ), + onClick: () => + KibanaLogic.values.navigateToUrl( + generateEncodedPath(CONNECTOR_DETAIL_TAB_PATH, { + connectorId, + tabId: ConnectorDetailTabId.PIPELINES, + }) + ), + }; + + interface TabMenuItem { + content: JSX.Element; + disabled?: boolean; + id: string; + label: string; + onClick?: () => void; + prepend?: React.ReactNode; + route?: string; + testSubj?: string; + } + + const tabs: TabMenuItem[] = [ + ...ALL_INDICES_TABS, + ...CONNECTOR_TABS, + ...(hasDefaultIngestPipeline ? [PIPELINES_TAB] : []), + ]; + + const selectedTab = tabs.find((tab) => tab.id === tabId); + + return ( + + {selectedTab?.content || null} + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/connector_detail_router.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/connector_detail_router.tsx new file mode 100644 index 0000000000000..539d549f2bd51 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/connector_detail_router.tsx @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useEffect } from 'react'; + +import { useActions } from 'kea'; + +import { Routes, Route } from '@kbn/shared-ux-router'; + +import { CONNECTOR_DETAIL_PATH, CONNECTOR_DETAIL_TAB_PATH } from '../../routes'; + +import { IndexNameLogic } from '../search_index/index_name_logic'; + +import { IndexViewLogic } from '../search_index/index_view_logic'; + +import { ConnectorDetail } from './connector_detail'; +import { ConnectorViewLogic } from './connector_view_logic'; + +export const ConnectorDetailRouter: React.FC = () => { + const { stopFetchIndexPoll } = useActions(IndexViewLogic); + useEffect(() => { + const unmountName = IndexNameLogic.mount(); + const unmountView = ConnectorViewLogic.mount(); + const unmountIndexView = IndexViewLogic.mount(); + return () => { + stopFetchIndexPoll(); + unmountName(); + unmountView(); + unmountIndexView(); + }; + }, []); + return ( + + + + + + + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/connector_stats.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/connector_stats.tsx new file mode 100644 index 0000000000000..89f75d979984e --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/connector_stats.tsx @@ -0,0 +1,235 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React, { ReactNode } from 'react'; + +import { + EuiBadge, + EuiFlexGrid, + EuiFlexGroup, + EuiFlexItem, + EuiHealth, + EuiIcon, + EuiSplitPanel, + EuiText, + EuiTitle, +} from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { Connector } from '@kbn/search-connectors'; + +import { ConnectorIndex } from '../../../../../common/types/indices'; + +import { generateEncodedPath } from '../../../shared/encode_path_params'; +import { EuiLinkTo } from '../../../shared/react_router_helpers'; +import { CONNECTOR_DETAIL_TAB_PATH } from '../../routes'; +import { + connectorStatusToColor, + connectorStatusToText, +} from '../../utils/connector_status_helpers'; + +import { CONNECTORS } from '../search_index/connector/constants'; + +import { ConnectorDetailTabId } from './connector_detail'; + +export interface ConnectorStatsProps { + connector: Connector; + indexData?: ConnectorIndex; +} + +export interface StatCardProps { + content: ReactNode; + footer: ReactNode; + title: string; +} + +export const StatCard: React.FC = ({ title, content, footer }) => { + return ( + + + + + +

{title}

+
+
+ {content} +
+
+ + {footer} + +
+ ); +}; + +export const ConnectorStats: React.FC = ({ connector, indexData }) => { + const connectorDefinition = CONNECTORS.find((c) => c.serviceType === connector.service_type); + return ( + + + + + + {connectorDefinition && connectorDefinition.icon && ( + + + + )} + + +

{connectorDefinition?.name ?? '-'}

+
+
+
+
+ + + {connectorStatusToText(connector?.status)} + + + + } + footer={ + + + + + + + + +

+ {i18n.translate( + 'xpack.enterpriseSearch.connectors.connectorStats.p.DocumentsLabel', + { + defaultMessage: '{documentAmount} Documents', + values: { + documentAmount: indexData?.total.docs.count ?? '-', + }, + } + )} +

+
+
+
+
+ + + + {i18n.translate( + 'xpack.enterpriseSearch.connectors.connectorStats.seeDocumentsTextLabel', + { + defaultMessage: 'See documents', + } + )} + + + +
+ } + /> +
+ + + + {connector.index_name} + + + + + + ) : ( + i18n.translate('xpack.enterpriseSearch.connectors.connectorStats.noIndex', { + defaultMessage: 'No index related', + }) + ) + } + footer={ + + + + + {i18n.translate( + 'xpack.enterpriseSearch.connectors.connectorStats.configureLink', + { + defaultMessage: 'Configure', + } + )} + + + + + } + /> + + + + + {connector.pipeline.name} + + + ) : ( + i18n.translate('xpack.enterpriseSearch.connectors.connectorStats.noPipelineText', { + defaultMessage: 'None', + }) + ) + } + footer={ + + + + + {i18n.translate( + 'xpack.enterpriseSearch.connectors.connectorStats.managePipelines', + { + defaultMessage: 'Manage pipelines', + } + )} + + + + + } + /> + +
+ ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/connector_view_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/connector_view_logic.ts new file mode 100644 index 0000000000000..31e41eef9c969 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/connector_view_logic.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 { kea, MakeLogicType } from 'kea'; + +import { Connector, FeatureName, IngestPipelineParams } from '@kbn/search-connectors'; + +import { Status } from '../../../../../common/types/api'; + +import { + FetchConnectorByIdApiLogic, + FetchConnectorByIdApiLogicActions, +} from '../../api/connector/fetch_connector_by_id_logic'; + +import { FetchIndexActions, FetchIndexApiLogic } from '../../api/index/fetch_index_api_logic'; +import { ElasticsearchViewIndex, IngestionMethod, IngestionStatus } from '../../types'; +import { IndexNameActions, IndexNameLogic } from '../search_index/index_name_logic'; + +export interface ConnectorViewActions { + fetchConnector: FetchConnectorByIdApiLogicActions['makeRequest']; + fetchConnectorApiError: FetchConnectorByIdApiLogicActions['apiError']; + fetchConnectorApiSuccess: FetchConnectorByIdApiLogicActions['apiSuccess']; + fetchIndex: FetchIndexActions['makeRequest']; + fetchIndexApiError: FetchIndexActions['apiError']; + fetchIndexApiSuccess: FetchIndexActions['apiSuccess']; + setIndexName: IndexNameActions['setIndexName']; +} + +// TODO UPDATE +export interface ConnectorViewValues { + connector: Connector | undefined; + connectorData: typeof FetchConnectorByIdApiLogic.values.data; + connectorError: string | undefined; + connectorId: string | null; + connectorName: string | null; + error: string | undefined; + fetchConnectorApiStatus: Status; + fetchIndexApiStatus: Status; + hasAdvancedFilteringFeature: boolean; + hasBasicFilteringFeature: boolean; + hasDocumentLevelSecurityFeature: boolean; + hasFilteringFeature: boolean; + hasIncrementalSyncFeature: boolean; + htmlExtraction: boolean | undefined; + index: ElasticsearchViewIndex | undefined; + indexName: string; + ingestionMethod: IngestionMethod; + ingestionStatus: IngestionStatus; + isCanceling: boolean; + isHiddenIndex: boolean; + isLoading: boolean; + isSyncing: boolean; + isWaitingForSync: boolean; + lastUpdated: string | null; + pipelineData: IngestPipelineParams | undefined; + recheckIndexLoading: boolean; + syncTriggeredLocally: boolean; // holds local value after update so UI updates correctly +} + +export const ConnectorViewLogic = kea>({ + actions: {}, + connect: { + actions: [ + IndexNameLogic, + ['setIndexName'], + FetchConnectorByIdApiLogic, + [ + 'makeRequest as fetchConnector', + 'apiSuccess as fetchConnectorApiSuccess', + 'apiError as fetchConnectorApiError', + ], + FetchIndexApiLogic, + [ + 'makeRequest as fetchIndex', + 'apiSuccess as fetchIndexApiSuccess', + 'apiError as fetchIndexApiError', + ], + ], + values: [ + FetchConnectorByIdApiLogic, + ['status as fetchConnectorApiStatus', 'data as connectorData'], + FetchIndexApiLogic, + ['data as index', 'status as fetchIndexApiStatus'], + ], + }, + listeners: ({ actions, values }) => ({ + fetchConnectorApiSuccess: () => { + if (values.indexName) { + actions.fetchIndex({ indexName: values.indexName }); + actions.setIndexName(values.indexName); + } + }, + }), + path: ['enterprise_search', 'content', 'connector_view_logic'], + reducers: { + syncTriggeredLocally: [ + false, + { + fetchIndexApiSuccess: () => false, + startSyncApiSuccess: () => true, + }, + ], + }, + selectors: ({ selectors }) => ({ + connector: [ + () => [selectors.connectorData], + (connectorData) => { + return connectorData?.connector; + }, + ], + indexName: [ + () => [selectors.connector], + (connector: Connector | undefined) => { + return connector?.index_name || undefined; + }, + ], + isLoading: [ + () => [selectors.fetchConnectorApiStatus, selectors.fetchIndexApiStatus], + (fetchConnectorApiStatus: Status, fetchIndexApiStatus: Status) => + [Status.IDLE && Status.LOADING].includes(fetchConnectorApiStatus) || + [Status.IDLE && Status.LOADING].includes(fetchIndexApiStatus), + ], + connectorId: [() => [selectors.connector], (connector) => connector?.id], + connectorError: [ + () => [selectors.connector], + (connector: Connector | undefined) => connector?.error, + ], + error: [ + () => [selectors.connector], + (connector: Connector | undefined) => connector?.error || connector?.last_sync_error || null, + ], + hasAdvancedFilteringFeature: [ + () => [selectors.connector], + (connector?: Connector) => + connector?.features + ? connector.features[FeatureName.SYNC_RULES]?.advanced?.enabled ?? + connector.features[FeatureName.FILTERING_ADVANCED_CONFIG] + : false, + ], + hasBasicFilteringFeature: [ + () => [selectors.connector], + (connector?: Connector) => + connector?.features + ? connector.features[FeatureName.SYNC_RULES]?.basic?.enabled ?? + connector.features[FeatureName.FILTERING_RULES] + : false, + ], + hasDocumentLevelSecurityFeature: [ + () => [selectors.connector], + (connector?: Connector) => + connector?.features?.[FeatureName.DOCUMENT_LEVEL_SECURITY]?.enabled || false, + ], + hasFilteringFeature: [ + () => [selectors.hasAdvancedFilteringFeature, selectors.hasBasicFilteringFeature], + (advancedFeature: boolean, basicFeature: boolean) => advancedFeature || basicFeature, + ], + hasIncrementalSyncFeature: [ + () => [selectors.connector], + (connector?: Connector) => + connector?.features?.[FeatureName.INCREMENTAL_SYNC]?.enabled || false, + ], + htmlExtraction: [ + () => [selectors.connector], + (connector: Connector | undefined) => + connector?.configuration.extract_full_html?.value ?? undefined, + ], + pipelineData: [ + () => [selectors.connector], + (connector: Connector | undefined) => connector?.pipeline ?? undefined, + ], + }), +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/overview.logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/overview.logic.ts new file mode 100644 index 0000000000000..790cd87f9b4e3 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/overview.logic.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 { kea, MakeLogicType } from 'kea'; + +import { Status } from '../../../../../common/types/api'; +import { KibanaLogic } from '../../../shared/kibana'; + +import { + CachedFetchIndexApiLogic, + CachedFetchIndexApiLogicActions, +} from '../../api/index/cached_fetch_index_api_logic'; + +import { CONNECTORS_PATH } from '../../routes'; + +interface OverviewLogicActions { + apiError: CachedFetchIndexApiLogicActions['apiError']; +} + +interface OverviewLogicValues { + apiKey: string; + indexData: typeof CachedFetchIndexApiLogic.values.indexData; + isError: boolean; + isLoading: boolean; + isManageKeysPopoverOpen: boolean; + status: typeof CachedFetchIndexApiLogic.values.status; +} + +export const OverviewLogic = kea>({ + connect: { + actions: [CachedFetchIndexApiLogic, ['apiError']], + values: [CachedFetchIndexApiLogic, ['indexData', 'status']], + }, + listeners: () => ({ + apiError: async (_, breakpoint) => { + // show error for a second before navigating away + await breakpoint(1000); + KibanaLogic.values.navigateToUrl(CONNECTORS_PATH); + }, + }), + path: ['enterprise_search', 'connector_detail', 'overview'], + selectors: ({ selectors }) => ({ + isError: [() => [selectors.status], (status) => status === Status.ERROR], + }), +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/overview.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/overview.tsx new file mode 100644 index 0000000000000..3e9a4af5ee3ad --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/overview.tsx @@ -0,0 +1,114 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { useActions, useValues } from 'kea'; + +import { EuiButton, EuiCallOut, EuiLink, EuiSpacer, EuiText } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { FormattedMessage } from '@kbn/i18n-react'; + +import { docLinks } from '../../../shared/doc_links'; +import { KibanaLogic } from '../../../shared/kibana'; +import { isConnectorIndex } from '../../utils/indices'; + +import { ConvertConnectorLogic } from '../search_index/connector/native_connector_configuration/convert_connector_logic'; +import { SyncJobs } from '../search_index/sync_jobs/sync_jobs'; + +import { ConvertConnectorModal } from '../shared/convert_connector_modal/convert_connector_modal'; + +import { ConnectorStats } from './connector_stats'; +import { ConnectorViewLogic } from './connector_view_logic'; +import { OverviewLogic } from './overview.logic'; + +export const ConnectorDetailOverview: React.FC = () => { + const { indexData } = useValues(OverviewLogic); + const { connector } = useValues(ConnectorViewLogic); + const error = null; + const { isCloud } = useValues(KibanaLogic); + const { showModal } = useActions(ConvertConnectorLogic); + const { isModalVisible } = useValues(ConvertConnectorLogic); + + return ( + <> + + {isConnectorIndex(indexData) && error && ( + <> + + + {error} + + + + )} + {isConnectorIndex(indexData) && indexData.connector.is_native && !isCloud && ( + <> + {isModalVisible && } + + + +

+ + {i18n.translate( + 'xpack.enterpriseSearch.content.connectors.overview.nativeCloudCallout.connectorClient', + { defaultMessage: 'connector client' } + )} + + ), + }} + /> +

+
+ + showModal()}> + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.connectors.overview.convertConnector.buttonLabel', + { defaultMessage: 'Convert connector' } + )} + +
+ + + )} + {isConnectorIndex(indexData) && connector && ( + + )} + {isConnectorIndex(indexData) && ( + <> + + + + )} + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/connectors_table.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/connectors_table.tsx index 2c675c362b00e..f7ba555a3d2f7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/connectors_table.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/connectors_table.tsx @@ -27,7 +27,7 @@ import { Meta } from '../../../../../common/types/pagination'; import { generateEncodedPath } from '../../../shared/encode_path_params'; import { KibanaLogic } from '../../../shared/kibana'; import { EuiLinkTo } from '../../../shared/react_router_helpers/eui_components'; -import { SEARCH_INDEX_PATH } from '../../routes'; +import { CONNECTOR_DETAIL_PATH, SEARCH_INDEX_PATH } from '../../routes'; import { connectorStatusToColor, connectorStatusToText, @@ -59,13 +59,17 @@ export const ConnectorsTable: React.FC = ({ const { navigateToUrl } = useValues(KibanaLogic); const columns: Array> = [ { - field: 'name', name: i18n.translate( 'xpack.enterpriseSearch.content.connectors.connectorTable.columns.connectorName', { defaultMessage: 'Connector name', } ), + render: (connector: Connector) => ( + + {connector.name} + + ), width: '25%', }, { @@ -161,8 +165,8 @@ export const ConnectorsTable: React.FC = ({ ), onClick: (connector) => { navigateToUrl( - generateEncodedPath(SEARCH_INDEX_PATH, { - indexName: connector.index_name || '', + generateEncodedPath(CONNECTOR_DETAIL_PATH, { + connectorId: connector.id, }) ); }, diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration.tsx index 93566c21fe999..e338b7d1f193b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration.tsx @@ -120,31 +120,10 @@ export const ConnectorConfiguration: React.FC = () => { { children: ( <> - - - {i18n.translate( - 'xpack.enterpriseSearch.content.indices.configurationConnector.connectorPackage.clientExamplesLink', - { defaultMessage: 'connector client examples' } - )} - - ), - }} - /> - - { title: i18n.translate( 'xpack.enterpriseSearch.content.indices.configurationConnector.steps.deployConnector.title', { - defaultMessage: 'Deploy connector', + defaultMessage: 'Deploy connector service', } ), titleSize: 'xs', @@ -270,7 +249,7 @@ export const ConnectorConfiguration: React.FC = () => { title: i18n.translate( 'xpack.enterpriseSearch.content.indices.configurationConnector.steps.enhance.title', { - defaultMessage: 'Enhance your connector client', + defaultMessage: 'Configure your connector client', } ), titleSize: 'xs', diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/index.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/index.tsx index 53fbd691476cf..cd99649bcc88d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/index.tsx @@ -20,12 +20,14 @@ import { HttpLogic } from '../shared/http'; import { KibanaLogic } from '../shared/kibana'; import { VersionMismatchPage } from '../shared/version_mismatch'; +import { ConnectorDetailRouter } from './components/connector_detail/connector_detail_router'; import { Connectors } from './components/connectors/connectors'; import { NotFound } from './components/not_found'; import { SearchIndicesRouter } from './components/search_indices'; import { Settings } from './components/settings'; import { CONNECTORS_PATH, + CONNECTOR_DETAIL_PATH, CRAWLERS_PATH, ERROR_STATE_PATH, ROOT_PATH, @@ -77,6 +79,9 @@ export const EnterpriseSearchContentConfigured: React.FC + + + diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/routes.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/routes.ts index 9cf8628780a2a..1b9445b7fa756 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/routes.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/routes.ts @@ -34,3 +34,6 @@ export const ML_MANAGE_TRAINED_MODELS_PATH = '/app/ml/trained_models'; export const ML_NOTIFICATIONS_PATH = '/app/ml/notifications'; export const DEV_TOOLS_CONSOLE_PATH = '/app/dev_tools#/console'; + +export const CONNECTOR_DETAIL_PATH = `${CONNECTORS_PATH}/:connectorId`; +export const CONNECTOR_DETAIL_TAB_PATH = `${CONNECTOR_DETAIL_PATH}/:tabId`; diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts index 91899c88d73fc..39657c97c6202 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts @@ -545,7 +545,32 @@ export function registerConnectorRoutes({ router, log }: RouteDependencies) { }); }) ); + router.get( + { + path: '/internal/enterprise_search/connectors/{connectorId}', + validate: { + params: schema.object({ + connectorId: schema.string(), + }), + }, + }, + elasticsearchErrorHandler(log, async (context, request, response) => { + const { client } = (await context.core).elasticsearch; + const { connectorId } = request.params; + let connectorResult; + try { + connectorResult = await fetchConnectorById(client.asCurrentUser, connectorId); + } catch (error) { + throw error; + } + return response.ok({ + body: { + connector: connectorResult?.value, + }, + }); + }) + ); router.delete( { path: '/internal/enterprise_search/connectors/{connectorId}', diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_validators.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_validators.test.tsx index 4352a3a6b8248..2e17153ee34e7 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_validators.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_validators.test.tsx @@ -142,6 +142,13 @@ describe('Output form validation', () => { expect(res).toBeUndefined(); }); + + it('should work with hostnames using uppercase letters', () => { + const res = validateLogstashHosts(['tEsT.fr:9200', 'TEST2.fr:9200', 'teSt.fR:9999']); + + expect(res).toBeUndefined(); + }); + it('should throw for invalid hosts starting with http', () => { const res = validateLogstashHosts(['https://test.fr:5044']); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_validators.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_validators.tsx index 330d5c5d20122..116e9f4abf157 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_validators.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_validators.tsx @@ -166,7 +166,7 @@ export function validateLogstashHosts(value: string[]) { const url = new URL(`http://${val}`); - if (url.host !== val) { + if (url.host !== val.toLowerCase()) { throw new Error('Invalid host'); } } catch (error) { diff --git a/x-pack/plugins/fleet/server/mocks/index.ts b/x-pack/plugins/fleet/server/mocks/index.ts index fb6dd7d075cea..857882c57525f 100644 --- a/x-pack/plugins/fleet/server/mocks/index.ts +++ b/x-pack/plugins/fleet/server/mocks/index.ts @@ -31,6 +31,8 @@ import { packageServiceMock } from '../services/epm/package_service.mock'; import type { UninstallTokenServiceInterface } from '../services/security/uninstall_token_service'; import type { MessageSigningServiceInterface } from '../services/security'; +import { PackagePolicyMocks } from './package_policy.mocks'; + // Export all mocks from artifacts export * from '../services/artifacts/mocks'; @@ -40,6 +42,8 @@ export * from '../services/files/mocks'; // export all mocks from fleet actions client export * from '../services/actions/mocks'; +export * from './package_policy.mocks'; + export interface MockedFleetAppContext extends FleetAppContext { elasticsearch: ReturnType; data: ReturnType; @@ -144,6 +148,22 @@ export const createPackagePolicyServiceMock = (): jest.Mocked { + return { + async *[Symbol.asyncIterator]() { + yield Promise.resolve([PackagePolicyMocks.generatePackagePolicy({ id: '111' })]); + yield Promise.resolve([PackagePolicyMocks.generatePackagePolicy({ id: '222' })]); + }, + }; + }), + fetchAllItemIds: jest.fn((..._) => { + return { + async *[Symbol.asyncIterator]() { + yield Promise.resolve(['111']); + yield Promise.resolve(['222']); + }, + }; + }), }; }; diff --git a/x-pack/plugins/fleet/server/mocks/package_policy.mocks.ts b/x-pack/plugins/fleet/server/mocks/package_policy.mocks.ts new file mode 100644 index 0000000000000..a159917cb5e17 --- /dev/null +++ b/x-pack/plugins/fleet/server/mocks/package_policy.mocks.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 type { SavedObjectsFindResponse } from '@kbn/core-saved-objects-api-server'; + +import type { SavedObjectsFindResult } from '@kbn/core-saved-objects-api-server'; + +import { mapPackagePolicySavedObjectToPackagePolicy } from '../services/package_policies'; + +import type { PackagePolicy } from '../../common'; +import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../../common'; + +import type { PackagePolicySOAttributes } from '../types'; + +const generatePackagePolicySOAttributesMock = ( + overrides: Partial = {} +): PackagePolicySOAttributes => { + return { + name: `Package Policy 1`, + description: 'Policy for things', + created_at: '2024-01-24T15:21:13.389Z', + created_by: 'elastic', + updated_at: '2024-01-25T15:21:13.389Z', + updated_by: 'user-a', + policy_id: '444-555-666', + enabled: true, + inputs: [], + namespace: 'default', + package: { + name: 'endpoint', + title: 'Elastic Endpoint', + version: '1.0.0', + }, + revision: 1, + is_managed: false, + secret_references: [], + vars: {}, + elasticsearch: { + privileges: { + cluster: [], + }, + }, + agents: 2, + + ...overrides, + }; +}; + +const generatePackagePolicyMock = (overrides: Partial = {}) => { + return { + ...mapPackagePolicySavedObjectToPackagePolicy(generatePackagePolicySavedObjectMock()), + ...overrides, + }; +}; + +const generatePackagePolicySavedObjectMock = ( + soAttributes: PackagePolicySOAttributes = generatePackagePolicySOAttributesMock() +): SavedObjectsFindResult => { + return { + score: 1, + id: 'so-123', + type: PACKAGE_POLICY_SAVED_OBJECT_TYPE, + version: 'abc', + created_at: soAttributes.created_at, + updated_at: soAttributes.updated_at, + attributes: soAttributes, + references: [], + sort: ['created_at'], + }; +}; + +const generatePackagePolicySavedObjectFindResponseMock = ( + soResults?: PackagePolicySOAttributes[] +): SavedObjectsFindResponse => { + const soList = soResults ?? [ + generatePackagePolicySOAttributesMock(), + generatePackagePolicySOAttributesMock(), + ]; + + return { + saved_objects: soList.map((soAttributes) => { + return { + score: 1, + id: 'so-123', + type: PACKAGE_POLICY_SAVED_OBJECT_TYPE, + version: 'abc', + created_at: soAttributes.created_at, + updated_at: soAttributes.updated_at, + attributes: soAttributes, + references: [], + sort: ['created_at'], + }; + }), + total: soList.length, + per_page: 10, + page: 1, + pit_id: 'pit-id-1', + }; +}; + +export const PackagePolicyMocks = Object.freeze({ + generatePackagePolicySOAttributes: generatePackagePolicySOAttributesMock, + generatePackagePolicySavedObjectFindResponse: generatePackagePolicySavedObjectFindResponseMock, + generatePackagePolicy: generatePackagePolicyMock, +}); diff --git a/x-pack/plugins/fleet/server/services/artifacts/artifacts.test.ts b/x-pack/plugins/fleet/server/services/artifacts/artifacts.test.ts index f3b332a5930fc..782b044a84697 100644 --- a/x-pack/plugins/fleet/server/services/artifacts/artifacts.test.ts +++ b/x-pack/plugins/fleet/server/services/artifacts/artifacts.test.ts @@ -11,6 +11,8 @@ import { errors } from '@elastic/elasticsearch'; import type { TransportResult } from '@elastic/elasticsearch'; +import { set } from '@kbn/safer-lodash-set'; + import { FLEET_SERVER_ARTIFACTS_INDEX } from '../../../common'; import { ArtifactsElasticsearchError } from '../../errors'; @@ -33,12 +35,14 @@ import { createArtifact, deleteArtifact, encodeArtifactContent, + fetchAllArtifacts, generateArtifactContentHash, getArtifact, listArtifacts, } from './artifacts'; import type { NewArtifact } from './types'; +import type { FetchAllArtifactsOptions } from './types'; describe('When using the artifacts services', () => { let esClientMock: ReturnType; @@ -324,8 +328,28 @@ describe('When using the artifacts services', () => { newArtifact, ]); - expect(responseErrors).toEqual([new Error('error')]); - expect(artifacts).toBeUndefined(); + expect(responseErrors).toEqual([ + new Error( + 'Create of artifact id [undefined] returned: result [undefined], status [400], reason [{"reason":"error"}]' + ), + ]); + expect(artifacts).toEqual([ + { + body: 'eJyrVkrNKynKTC1WsoqOrQUAJxkFKQ==', + compressionAlgorithm: 'zlib', + created: expect.any(String), + decodedSha256: 'd801aa1fb', + decodedSize: 14, + encodedSha256: 'd29238d40', + encodedSize: 22, + encryptionAlgorithm: 'none', + id: 'endpoint:trustlist-v1-d801aa1fb', + identifier: 'trustlist-v1', + packageName: 'endpoint', + relative_url: '/api/fleet/artifacts/trustlist-v1/d801aa1fb', + type: 'trustlist', + }, + ]); }); }); @@ -488,4 +512,113 @@ describe('When using the artifacts services', () => { }); }); }); + + describe('and calling `fetchAll()`', () => { + beforeEach(() => { + esClientMock.search + .mockResolvedValueOnce(generateArtifactEsSearchResultHitsMock()) + .mockResolvedValueOnce(generateArtifactEsSearchResultHitsMock()) + .mockResolvedValueOnce(set(generateArtifactEsSearchResultHitsMock(), 'hits.hits', [])); + }); + + it('should return an iterator', async () => { + expect(fetchAllArtifacts(esClientMock)).toEqual({ + [Symbol.asyncIterator]: expect.any(Function), + }); + }); + + it('should provide artifacts on each iteration', async () => { + for await (const artifacts of fetchAllArtifacts(esClientMock)) { + expect(artifacts[0]).toEqual({ + body: expect.anything(), + compressionAlgorithm: expect.anything(), + created: expect.anything(), + decodedSha256: expect.anything(), + decodedSize: expect.anything(), + encodedSha256: expect.anything(), + encodedSize: expect.anything(), + encryptionAlgorithm: expect.anything(), + id: expect.anything(), + identifier: expect.anything(), + packageName: expect.anything(), + relative_url: expect.anything(), + type: expect.anything(), + }); + } + + expect(esClientMock.search).toHaveBeenCalledTimes(3); + }); + + it('should use defaults if no `options` were provided', async () => { + for await (const artifacts of fetchAllArtifacts(esClientMock)) { + expect(artifacts.length).toBeGreaterThan(0); + } + + expect(esClientMock.search).toHaveBeenLastCalledWith( + expect.objectContaining({ + q: '', + size: 1000, + sort: [{ created: { order: 'asc' } }], + _source_excludes: undefined, + }) + ); + }); + + it('should use custom options when provided', async () => { + const options: FetchAllArtifactsOptions = { + kuery: 'foo: something', + sortOrder: 'desc', + perPage: 500, + sortField: 'someField', + includeArtifactBody: false, + }; + + for await (const artifacts of fetchAllArtifacts(esClientMock, options)) { + expect(artifacts.length).toBeGreaterThan(0); + } + + expect(esClientMock.search).toHaveBeenCalledWith( + expect.objectContaining({ + q: options.kuery, + size: options.perPage, + sort: [{ [options.sortField!]: { order: options.sortOrder } }], + _source_excludes: 'body', + }) + ); + }); + + it('should set `done` to true if loop `break`s out', async () => { + const iterator = fetchAllArtifacts(esClientMock); + + for await (const _ of iterator) { + break; + } + + await expect(iterator[Symbol.asyncIterator]().next()).resolves.toEqual({ + done: true, + value: expect.any(Array), + }); + + expect(esClientMock.search).toHaveBeenCalledTimes(1); + }); + + it('should handle throwing in loop by setting `done` to `true`', async () => { + const iterator = fetchAllArtifacts(esClientMock); + + try { + for await (const _ of iterator) { + throw new Error('test'); + } + } catch (e) { + expect(e); // just to silence eslint + } + + await expect(iterator[Symbol.asyncIterator]().next()).resolves.toEqual({ + done: true, + value: expect.any(Array), + }); + + expect(esClientMock.search).toHaveBeenCalledTimes(1); + }); + }); }); diff --git a/x-pack/plugins/fleet/server/services/artifacts/artifacts.ts b/x-pack/plugins/fleet/server/services/artifacts/artifacts.ts index 5516ab6f70e23..43cf3f745cc6c 100644 --- a/x-pack/plugins/fleet/server/services/artifacts/artifacts.ts +++ b/x-pack/plugins/fleet/server/services/artifacts/artifacts.ts @@ -15,6 +15,8 @@ import { isEmpty, sortBy } from 'lodash'; import type { ElasticsearchClient } from '@kbn/core/server'; +import { createEsSearchIterable } from '../utils/create_es_search_iterable'; + import type { ListResult } from '../../../common/types'; import { FLEET_SERVER_ARTIFACTS_INDEX } from '../../../common'; @@ -34,6 +36,7 @@ import type { ArtifactsClientCreateOptions, ListArtifactsProps, NewArtifact, + FetchAllArtifactsOptions, } from './types'; import { esSearchHitToArtifact, @@ -137,10 +140,10 @@ export const bulkCreateArtifacts = async ( artifacts, appContextService.getConfig()?.createArtifactsBulkBatchSize ); - const logger = appContextService.getLogger(); const nonConflictErrors = []; logger.debug(`Number of batches generated for fleet artifacts: ${batches.length}`); + for (let batchN = 0; batchN < batches.length; batchN++) { logger.debug( `Creating artifacts for batch ${batchN + 1} with ${batches[batchN].length / 2} artifacts` @@ -154,12 +157,22 @@ export const bulkCreateArtifacts = async ( refresh, }) ); + // Track errors of the bulk create action if (res.errors) { nonConflictErrors.push( ...res.items.reduce((acc, item) => { - if (item.create?.status !== 409) { - acc.push(new Error(item.create?.error?.reason)); + // 409's (conflict - record already exists) are ignored since the artifact already exists + if (item.create && item.create.status !== 409) { + acc.push( + new Error( + `Create of artifact id [${item.create._id}] returned: result [${ + item.create.result + }], status [${item.create.status}], reason [${JSON.stringify( + item.create?.error || '' + )}]` + ) + ); } return acc; }, []) @@ -167,11 +180,6 @@ export const bulkCreateArtifacts = async ( } } - // If any non conflict error, it returns only the errors - if (nonConflictErrors.length > 0) { - return { errors: nonConflictErrors }; - } - // Use non sorted artifacts array to preserve the artifacts order in the response const nonSortedEsArtifactsResponse: Artifact[] = artifacts.map((artifact) => { return esSearchHitToArtifact({ @@ -182,6 +190,7 @@ export const bulkCreateArtifacts = async ( return { artifacts: nonSortedEsArtifactsResponse, + errors: nonConflictErrors.length ? nonConflictErrors : undefined, }; }; @@ -281,3 +290,66 @@ export const encodeArtifactContent = async ( return encodedArtifact; }; + +/** + * Returns an iterator that loops through all the artifacts stored in the index + * + * @param esClient + * @param options + * + * @example + * + * async () => { + * for await (const value of fetchAllArtifactsIterator()) { + * // process page of data here + * } + * } + */ +export const fetchAllArtifacts = ( + esClient: ElasticsearchClient, + options: FetchAllArtifactsOptions = {} +): AsyncIterable => { + const { kuery = '', perPage = 1000, sortField, sortOrder, includeArtifactBody = true } = options; + + return createEsSearchIterable({ + esClient, + searchRequest: { + index: FLEET_SERVER_ARTIFACTS_INDEX, + rest_total_hits_as_int: true, + track_total_hits: false, + q: kuery, + size: perPage, + sort: [ + { + // MUST have a sort field and sort order + [sortField || 'created']: { + order: sortOrder || 'asc', + }, + }, + ], + _source_excludes: includeArtifactBody ? undefined : 'body', + }, + resultsMapper: (data): Artifact[] => { + return data.hits.hits.map((hit) => { + // @ts-expect-error @elastic/elasticsearch _source is optional + const artifact = esSearchHitToArtifact(hit); + + // If not body attribute is included, still create the property in the object (since the + // return type is `Artifact` and `body` is required), but throw an error is caller attempts + // to still access it. + if (!includeArtifactBody) { + Object.defineProperty(artifact, 'body', { + enumerable: false, + get(): string { + throw new Error( + `'body' attribute not included due to request to 'fetchAllArtifacts()' having options 'includeArtifactBody' set to 'false'` + ); + }, + }); + } + + return artifact; + }); + }, + }); +}; diff --git a/x-pack/plugins/fleet/server/services/artifacts/client.ts b/x-pack/plugins/fleet/server/services/artifacts/client.ts index 7ba2452e83fe7..0b40a7acdcc8d 100644 --- a/x-pack/plugins/fleet/server/services/artifacts/client.ts +++ b/x-pack/plugins/fleet/server/services/artifacts/client.ts @@ -17,6 +17,7 @@ import type { ArtifactsClientInterface, NewArtifact, ListArtifactsProps, + FetchAllArtifactsOptions, } from './types'; import { relativeDownloadUrlFromArtifact, uniqueIdFromId } from './mappings'; @@ -29,6 +30,7 @@ import { listArtifacts, bulkCreateArtifacts, bulkDeleteArtifacts, + fetchAllArtifacts, } from './artifacts'; /** @@ -49,6 +51,15 @@ export class FleetArtifactsClient implements ArtifactsClientInterface { return artifact; } + /** + * Creates a `kuery` string using the provided value on input that is bound to the integration package + * @param kuery + * @private + */ + private buildFilter(kuery: string): string { + return `(package_name: "${this.packageName}")${kuery ? ` AND ${kuery}` : ''}`; + } + async getArtifact(id: string): Promise { const artifact = await getArtifact(this.esClient, id); return artifact ? this.validate(artifact) : undefined; @@ -119,20 +130,37 @@ export class FleetArtifactsClient implements ArtifactsClientInterface { } /** - * Get a list of artifacts. - * NOTE that when using the `kuery` filtering param, that all filters property names should - * match the internal attribute names of the index + * Get a list of artifacts. A few things to note: + * - if wanting to get ALL artifacts, consider using instead the `fetchAll()` method instead + * as it will property return data past the 10k ES limitation + * - when using the `kuery` filtering param, all filters property names should match the + * internal attribute names in the index */ async listArtifacts({ kuery, ...options }: ListArtifactsProps = {}): Promise< ListResult > { - // All filtering for artifacts should be bound to the `packageName`, so we insert - // that into the KQL value and use `AND` to add the defined `kuery` (if any) to it. - const filter = `(package_name: "${this.packageName}")${kuery ? ` AND ${kuery}` : ''}`; - return listArtifacts(this.esClient, { ...options, - kuery: filter, + kuery: this.buildFilter(kuery), + }); + } + + /** + * Returns an `AsyncIterable` object that can be used to iterate over all artifacts + * + * @param options + * + * @example + * async () => { + * for await (const artifacts of fleetArtifactsClient.fetchAll()) { + * // artifacts === first page of items + * } + * } + */ + fetchAll({ kuery, ...options }: FetchAllArtifactsOptions = {}): AsyncIterable { + return fetchAllArtifacts(this.esClient, { + ...options, + kuery: this.buildFilter(kuery), }); } diff --git a/x-pack/plugins/fleet/server/services/artifacts/mocks.ts b/x-pack/plugins/fleet/server/services/artifacts/mocks.ts index dc831558cb7bb..4e5d8c93f0643 100644 --- a/x-pack/plugins/fleet/server/services/artifacts/mocks.ts +++ b/x-pack/plugins/fleet/server/services/artifacts/mocks.ts @@ -44,6 +44,34 @@ export const createArtifactsClientMock = (): jest.Mocked { + return createFetchAllArtifactsIterableMock(); + }), + }; +}; + +export const createFetchAllArtifactsIterableMock = (artifactPages: Artifact[][] = []) => { + const totalPagesOfResults = artifactPages.length; + let nextResults = 0; + + return { + [Symbol.asyncIterator]() { + return { + async next() { + return { + value: artifactPages[nextResults++] ?? [], + done: nextResults > totalPagesOfResults, + }; + }, + + async return() { + return { + value: [], + done: true, + }; + }, + }; + }, }; }; @@ -100,6 +128,7 @@ export const generateArtifactEsGetSingleHitMock = ( _version: 1, _score: 1, _source, + sort: ['abc'], }; }; diff --git a/x-pack/plugins/fleet/server/services/artifacts/types.ts b/x-pack/plugins/fleet/server/services/artifacts/types.ts index 4b0aacd92bc20..697815a593fdd 100644 --- a/x-pack/plugins/fleet/server/services/artifacts/types.ts +++ b/x-pack/plugins/fleet/server/services/artifacts/types.ts @@ -72,6 +72,12 @@ export type ListArtifactsProps = Pick & { + sortField?: string | keyof ArtifactElasticsearchProperties; + /** If false, then the `body` property of the Artifact will be excluded from the results. Default is `true` */ + includeArtifactBody?: boolean; +}; + /** * The interface exposed out of Fleet's Artifact service via the client class */ @@ -93,4 +99,6 @@ export interface ArtifactsClientInterface { encodeContent(content: ArtifactsClientCreateOptions['content']): Promise; generateHash(content: string): string; + + fetchAll(options?: FetchAllArtifactsOptions): AsyncIterable; } 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 6c79ff1e59d41..073f93be81d6c 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/install.ts @@ -562,13 +562,13 @@ async function installPackageCommon(options: { return { error: err, installType, installSource }; } - const savedObjectsImporter = appContextService - .getSavedObjects() - .createImporter(savedObjectsClient, { importSizeLimit: 15_000 }); - // Saved object client need to be scopped with the package space for saved object tagging const savedObjectClientWithSpace = appContextService.getInternalUserSOClientForSpaceId(spaceId); + const savedObjectsImporter = appContextService + .getSavedObjects() + .createImporter(savedObjectClientWithSpace, { importSizeLimit: 15_000 }); + const savedObjectTagAssignmentService = appContextService .getSavedObjectsTagging() .createInternalAssignmentService({ client: savedObjectClientWithSpace }); diff --git a/x-pack/plugins/fleet/server/services/epm/registry/index.ts b/x-pack/plugins/fleet/server/services/epm/registry/index.ts index 97402447be716..9dfd307725945 100644 --- a/x-pack/plugins/fleet/server/services/epm/registry/index.ts +++ b/x-pack/plugins/fleet/server/services/epm/registry/index.ts @@ -94,16 +94,8 @@ async function _fetchFindLatestPackage( const bundledPackage = await getBundledPackageByName(packageName); - // temporary workaround to allow synthetics package beta version until there is a GA available - // needed because synthetics is installed by default on kibana startup - const prereleaseAllowedExceptions = ['synthetics']; - - const prereleaseEnabled = prerelease || prereleaseAllowedExceptions.includes(packageName); - const registryUrl = getRegistryUrl(); - const url = new URL( - `${registryUrl}/search?package=${packageName}&prerelease=${prereleaseEnabled}` - ); + const url = new URL(`${registryUrl}/search?package=${packageName}&prerelease=${prerelease}`); if (!ignoreConstraints) { setConstraints(url); diff --git a/x-pack/plugins/fleet/server/services/package_policies/index.ts b/x-pack/plugins/fleet/server/services/package_policies/index.ts index d0d4fa4aae825..a7eacdc76a3a7 100644 --- a/x-pack/plugins/fleet/server/services/package_policies/index.ts +++ b/x-pack/plugins/fleet/server/services/package_policies/index.ts @@ -7,3 +7,4 @@ export * from './experimental_datastream_features'; export * from './package_policy_name_helper'; +export * from './utils'; diff --git a/x-pack/plugins/fleet/server/services/package_policies/utils.test.ts b/x-pack/plugins/fleet/server/services/package_policies/utils.test.ts new file mode 100644 index 0000000000000..363ffe9c38fa4 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/package_policies/utils.test.ts @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { PackagePolicyMocks } from '../../mocks'; + +import { mapPackagePolicySavedObjectToPackagePolicy } from './utils'; + +describe('Package Policy Utils', () => { + describe('mapPackagePolicySavedObjectToPackagePolicy()', () => { + it('should return only exposed SO properties', () => { + const soItem = + PackagePolicyMocks.generatePackagePolicySavedObjectFindResponse().saved_objects.at(0)!; + + expect(mapPackagePolicySavedObjectToPackagePolicy(soItem)).toEqual({ + agents: 2, + created_at: '2024-01-24T15:21:13.389Z', + created_by: 'elastic', + description: 'Policy for things', + elasticsearch: { + privileges: { + cluster: [], + }, + }, + enabled: true, + id: 'so-123', + inputs: [], + is_managed: false, + name: 'Package Policy 1', + namespace: 'default', + package: { + name: 'endpoint', + title: 'Elastic Endpoint', + version: '1.0.0', + }, + policy_id: '444-555-666', + revision: 1, + secret_references: [], + updated_at: '2024-01-25T15:21:13.389Z', + updated_by: 'user-a', + vars: {}, + version: 'abc', + }); + }); + }); +}); diff --git a/x-pack/plugins/fleet/server/services/package_policies/utils.ts b/x-pack/plugins/fleet/server/services/package_policies/utils.ts new file mode 100644 index 0000000000000..309db211bbf14 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/package_policies/utils.ts @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { SavedObject } from '@kbn/core-saved-objects-common/src/server_types'; + +import type { PackagePolicy, PackagePolicySOAttributes } from '../../types'; + +export const mapPackagePolicySavedObjectToPackagePolicy = ({ + /* eslint-disable @typescript-eslint/naming-convention */ + id, + version, + attributes: { + name, + description, + namespace, + enabled, + is_managed, + policy_id, + // `package` is a reserved keyword + package: packageInfo, + inputs, + vars, + elasticsearch, + agents, + revision, + secret_references, + updated_at, + updated_by, + created_at, + created_by, + /* eslint-enable @typescript-eslint/naming-convention */ + }, +}: SavedObject): PackagePolicy => { + return { + id, + name, + description, + namespace, + enabled, + is_managed, + policy_id, + package: packageInfo, + inputs, + vars, + elasticsearch, + version, + agents, + revision, + secret_references, + updated_at, + updated_by, + created_at, + created_by, + }; +}; diff --git a/x-pack/plugins/fleet/server/services/package_policy.test.ts b/x-pack/plugins/fleet/server/services/package_policy.test.ts index 24483be93a9f5..cc605900c3a58 100644 --- a/x-pack/plugins/fleet/server/services/package_policy.test.ts +++ b/x-pack/plugins/fleet/server/services/package_policy.test.ts @@ -19,6 +19,8 @@ import type { } from '@kbn/core/server'; import { SavedObjectsErrorHelpers } from '@kbn/core/server'; +import { PackagePolicyMocks } from '../mocks/package_policy.mocks'; + import type { PackageInfo, PackagePolicySOAttributes, @@ -53,6 +55,8 @@ import { import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../constants'; +import { mapPackagePolicySavedObjectToPackagePolicy } from './package_policies'; + import { preconfigurePackageInputs, updatePackageInputs, @@ -4918,6 +4922,149 @@ describe('Package policy service', () => { ).rejects.toEqual(new FleetError('Package notinstalled is not installed')); }); }); + + describe('fetchAllItemIds()', () => { + let soClientMock: ReturnType; + + beforeEach(() => { + soClientMock = savedObjectsClientMock.create(); + + soClientMock.find + .mockResolvedValueOnce(PackagePolicyMocks.generatePackagePolicySavedObjectFindResponse()) + .mockResolvedValueOnce(PackagePolicyMocks.generatePackagePolicySavedObjectFindResponse()) + .mockResolvedValueOnce( + Object.assign(PackagePolicyMocks.generatePackagePolicySavedObjectFindResponse(), { + saved_objects: [], + }) + ); + }); + + it('should return an iterator', async () => { + expect(packagePolicyService.fetchAllItemIds(soClientMock)).toEqual({ + [Symbol.asyncIterator]: expect.any(Function), + }); + }); + + it('should provide item ids on every iteration', async () => { + for await (const ids of packagePolicyService.fetchAllItemIds(soClientMock)) { + expect(ids).toEqual(['so-123', 'so-123']); + } + + expect(soClientMock.find).toHaveBeenCalledTimes(3); + }); + + it('should use default options', async () => { + for await (const ids of packagePolicyService.fetchAllItemIds(soClientMock)) { + expect(ids); + } + + expect(soClientMock.find).toHaveBeenCalledWith( + expect.objectContaining({ + type: PACKAGE_POLICY_SAVED_OBJECT_TYPE, + perPage: 1000, + sortField: 'created_at', + sortOrder: 'asc', + fields: [], + filter: undefined, + }) + ); + }); + + it('should use custom options when defined', async () => { + for await (const ids of packagePolicyService.fetchAllItemIds(soClientMock, { + perPage: 13, + kuery: 'one=two', + })) { + expect(ids); + } + + expect(soClientMock.find).toHaveBeenCalledWith( + expect.objectContaining({ + type: PACKAGE_POLICY_SAVED_OBJECT_TYPE, + perPage: 13, + sortField: 'created_at', + sortOrder: 'asc', + fields: [], + filter: 'one=two', + }) + ); + }); + }); + + describe('fetchAllItems()', () => { + let soClientMock: ReturnType; + + beforeEach(() => { + soClientMock = savedObjectsClientMock.create(); + + soClientMock.find + .mockResolvedValueOnce(PackagePolicyMocks.generatePackagePolicySavedObjectFindResponse()) + .mockResolvedValueOnce(PackagePolicyMocks.generatePackagePolicySavedObjectFindResponse()) + .mockResolvedValueOnce( + Object.assign(PackagePolicyMocks.generatePackagePolicySavedObjectFindResponse(), { + saved_objects: [], + }) + ); + }); + + it('should return an iterator', async () => { + expect(packagePolicyService.fetchAllItems(soClientMock)).toEqual({ + [Symbol.asyncIterator]: expect.any(Function), + }); + }); + + it('should provide items on every iteration', async () => { + for await (const items of packagePolicyService.fetchAllItems(soClientMock)) { + expect(items).toEqual( + PackagePolicyMocks.generatePackagePolicySavedObjectFindResponse().saved_objects.map( + (soItem) => { + return mapPackagePolicySavedObjectToPackagePolicy(soItem); + } + ) + ); + } + + expect(soClientMock.find).toHaveBeenCalledTimes(3); + }); + + it('should use default options', async () => { + for await (const ids of packagePolicyService.fetchAllItemIds(soClientMock)) { + expect(ids); + } + + expect(soClientMock.find).toHaveBeenCalledWith( + expect.objectContaining({ + type: PACKAGE_POLICY_SAVED_OBJECT_TYPE, + perPage: 1000, + sortField: 'created_at', + sortOrder: 'asc', + fields: [], + filter: undefined, + }) + ); + }); + + it('should use custom options when defined', async () => { + for await (const ids of packagePolicyService.fetchAllItems(soClientMock, { + kuery: 'one=two', + perPage: 12, + sortOrder: 'desc', + sortField: 'updated_by', + })) { + expect(ids); + } + + expect(soClientMock.find).toHaveBeenCalledWith( + expect.objectContaining({ + type: PACKAGE_POLICY_SAVED_OBJECT_TYPE, + perPage: 12, + sortField: 'updated_by', + sortOrder: 'desc', + filter: 'one=two', + }) + ); + }); + }); }); describe('getUpgradeDryRunDiff', () => { diff --git a/x-pack/plugins/fleet/server/services/package_policy.ts b/x-pack/plugins/fleet/server/services/package_policy.ts index e89ac0160f62c..45753540af256 100644 --- a/x-pack/plugins/fleet/server/services/package_policy.ts +++ b/x-pack/plugins/fleet/server/services/package_policy.ts @@ -95,6 +95,8 @@ import type { } from '../types'; import type { ExternalCallback } from '..'; +import { createSoFindIterable } from './utils/create_so_find_iterable'; + import type { FleetAuthzRouteConfig } from './security'; import { getAuthzFromRequest, doesNotHaveRequiredFleetAuthz } from './security'; @@ -109,9 +111,16 @@ import { appContextService } from '.'; import { removeOldAssets } from './epm/packages/cleanup'; import type { PackageUpdateEvent, UpdateEventType } from './upgrade_sender'; import { sendTelemetryEvents } from './upgrade_sender'; -import { handleExperimentalDatastreamFeatureOptIn } from './package_policies'; +import { + handleExperimentalDatastreamFeatureOptIn, + mapPackagePolicySavedObjectToPackagePolicy, +} from './package_policies'; import { updateDatastreamExperimentalFeatures } from './epm/packages/update'; -import type { PackagePolicyClient, PackagePolicyService } from './package_policy_service'; +import type { + PackagePolicyClient, + PackagePolicyClientFetchAllItemsOptions, + PackagePolicyService, +} from './package_policy_service'; import { installAssetsForInputPackagePolicy } from './epm/packages/install'; import { auditLoggingService } from './audit_logging'; import { @@ -122,6 +131,7 @@ import { } from './secrets'; import { getPackageAssetsMap } from './epm/packages/get'; import { validateOutputForNewPackagePolicy } from './agent_policies/outputs_helpers'; +import type { PackagePolicyClientFetchAllItemIdsOptions } from './package_policy_service'; export type InputsOverride = Partial & { vars?: Array; @@ -1886,6 +1896,60 @@ class PackagePolicyClientImpl implements PackagePolicyClient { } } } + + fetchAllItemIds( + soClient: SavedObjectsClientContract, + { perPage = 1000, kuery }: PackagePolicyClientFetchAllItemIdsOptions = {} + ): AsyncIterable { + // TODO:PT Question for fleet team: do I need to `auditLoggingService.writeCustomSoAuditLog()` here? Its only IDs + + return createSoFindIterable<{}>({ + soClient, + findRequest: { + type: SAVED_OBJECT_TYPE, + perPage, + sortField: 'created_at', + sortOrder: 'asc', + fields: [], + filter: kuery ? normalizeKuery(SAVED_OBJECT_TYPE, kuery) : undefined, + }, + resultsMapper: (data) => { + return data.saved_objects.map((packagePolicySO) => packagePolicySO.id); + }, + }); + } + + fetchAllItems( + soClient: SavedObjectsClientContract, + { + perPage = 1000, + kuery, + sortOrder = 'asc', + sortField = 'created_at', + }: PackagePolicyClientFetchAllItemsOptions = {} + ): AsyncIterable { + return createSoFindIterable({ + soClient, + findRequest: { + type: SAVED_OBJECT_TYPE, + sortField, + sortOrder, + perPage, + filter: kuery ? normalizeKuery(SAVED_OBJECT_TYPE, kuery) : undefined, + }, + resultsMapper(data) { + return data.saved_objects.map((packagePolicySO) => { + auditLoggingService.writeCustomSoAuditLog({ + action: 'find', + id: packagePolicySO.id, + savedObjectType: PACKAGE_POLICY_SAVED_OBJECT_TYPE, + }); + + return mapPackagePolicySavedObjectToPackagePolicy(packagePolicySO); + }); + }, + }); + } } export class PackagePolicyServiceImpl diff --git a/x-pack/plugins/fleet/server/services/package_policy_service.ts b/x-pack/plugins/fleet/server/services/package_policy_service.ts index 9519cafbc6a73..de960c44b7879 100644 --- a/x-pack/plugins/fleet/server/services/package_policy_service.ts +++ b/x-pack/plugins/fleet/server/services/package_policy_service.ts @@ -213,4 +213,31 @@ export interface PackagePolicyClient { packageInfo: PackageInfo; experimentalDataStreamFeatures: ExperimentalDataStreamFeature[]; }>; + + /** + * Returns an `AsyncIterable` for retrieving all integration policy IDs + * @param soClient + * @param options + */ + fetchAllItemIds( + soClient: SavedObjectsClientContract, + options?: PackagePolicyClientFetchAllItemIdsOptions + ): AsyncIterable; + + /** + * Returns an `AsyncIterable` for retrieving all integration policies + * @param soClient + * @param options + */ + fetchAllItems( + soClient: SavedObjectsClientContract, + options?: PackagePolicyClientFetchAllItemsOptions + ): AsyncIterable; } + +export type PackagePolicyClientFetchAllItemIdsOptions = Pick; + +export type PackagePolicyClientFetchAllItemsOptions = Pick< + ListWithKuery, + 'perPage' | 'kuery' | 'sortField' | 'sortOrder' +>; diff --git a/x-pack/plugins/fleet/server/services/utils/create_es_search_iterable.ts b/x-pack/plugins/fleet/server/services/utils/create_es_search_iterable.ts new file mode 100644 index 0000000000000..ae4cb9551bc8c --- /dev/null +++ b/x-pack/plugins/fleet/server/services/utils/create_es_search_iterable.ts @@ -0,0 +1,165 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; + +import type * as estypes from '@kbn/es-types'; + +import type { SearchRequest, SearchResponse } from '@elastic/elasticsearch/lib/api/types'; + +export interface CreateEsSearchIterableOptions { + esClient: ElasticsearchClient; + searchRequest: Omit & + Pick, 'sort' | 'index'>; + /** + * An optional callback for mapping the results retrieved from ES. If defined, the iterator + * `value` will be set to the data returned by this mapping function. + * + * @param data + */ + resultsMapper?: (data: SearchResponse) => any; + /** If a Point in Time should be used while executing the search. Defaults to `true` */ + usePointInTime?: boolean; +} + +export type InferEsSearchIteratorResultValue = + CreateEsSearchIterableOptions['resultsMapper'] extends undefined + ? SearchResponse + : ReturnType>['resultsMapper']>; + +/** + * Creates an `AsyncIterable` that can be used to iterate (ex. via `for..await..of`) over all the data + * matching the search query. The search request to ES will use `search_after`, thus can iterate over + * datasets above 10k items as well. + * + * @param options + * + * @example + * + * const yourFn = async () => { + * const dataIterable = createEsSearchIterable({ + * esClient, + * searchRequest: { + * index: 'some-index', + * sort: [ + * { + * created: { order: 'asc' } + * } + * ] + * } + * }); + * + * for await (const data of dataIterable) { + * // data === your search results + * } + * } + */ +export const createEsSearchIterable = ({ + esClient, + searchRequest: { size = 1000, index, ...searchOptions }, + resultsMapper, + usePointInTime = true, +}: CreateEsSearchIterableOptions): AsyncIterable< + InferEsSearchIteratorResultValue +> => { + const keepAliveValue = '5m'; + let done = false; + let value: SearchResponse; + let searchAfterValue: estypes.SearchHit['sort'] | undefined; + let pointInTime: Promise<{ id: string }> = usePointInTime + ? esClient.openPointInTime({ + index, + ignore_unavailable: true, + keep_alive: keepAliveValue, + }) + : Promise.resolve({ id: '' }); + + const createIteratorResult = (): IteratorResult> => { + return { done, value }; + }; + + const setValue = (searchResponse: SearchResponse): void => { + value = resultsMapper ? resultsMapper(searchResponse) : searchResponse; + }; + + const setDone = async (): Promise => { + done = true; + + if (usePointInTime) { + const pitId = (await pointInTime).id; + + if (pitId) { + await esClient.closePointInTime({ id: pitId }); + } + } + }; + + const fetchData = async () => { + const pitId = (await pointInTime).id; + + const searchResult = await esClient + .search({ + ...searchOptions, + size, + ...(usePointInTime + ? { + pit: { + id: pitId, + keep_alive: keepAliveValue, + }, + } + : { index }), + search_after: searchAfterValue, + }) + .catch((e) => { + Error.captureStackTrace(e); + throw e; + }); + + const searchHits = searchResult.hits.hits; + const lastSearchHit = searchHits[searchHits.length - 1]; + + if (searchHits.length === 0) { + await setDone(); + return; + } + + searchAfterValue = lastSearchHit.sort; + pointInTime = Promise.resolve({ id: searchResult.pit_id ?? '' }); + setValue(searchResult); + + // If (for some reason) we don't have a `searchAfterValue`, + // then throw an error, or else we'll keep looping forever + if (!searchAfterValue) { + await setDone(); + throw new Error( + `Unable to store 'search_after' value. Last 'SearchHit' did not include a 'sort' property \n(did you forget to set the 'sort' attribute on your SearchRequest?)':\n${JSON.stringify( + lastSearchHit + )}` + ); + } + }; + + return { + [Symbol.asyncIterator]() { + return { + async next() { + if (!done) { + await fetchData(); + } + + return createIteratorResult(); + }, + + async return() { + done = true; + return createIteratorResult(); + }, + }; + }, + }; +}; diff --git a/x-pack/plugins/fleet/server/services/utils/create_so_find_iterable.ts b/x-pack/plugins/fleet/server/services/utils/create_so_find_iterable.ts new file mode 100644 index 0000000000000..6b17b3ba98040 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/utils/create_so_find_iterable.ts @@ -0,0 +1,142 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { + SavedObjectsClientContract, + SavedObjectsFindOptions, + SavedObjectsFindResponse, + SavedObjectsFindResult, +} from '@kbn/core-saved-objects-api-server'; + +export interface CreateSoFindIterableOptions { + soClient: SavedObjectsClientContract; + findRequest: Omit & + // sortField is required + Pick, 'sortField'>; + /** + * An optional callback for mapping the results retrieved from SavedObjects. If defined, the iterator + * `value` will be set to the data returned by this mapping function. + * + * @param data + */ + resultsMapper?: (data: SavedObjectsFindResponse) => any; + /** If a Point in Time should be used while executing the search. Defaults to `true` */ + usePointInTime?: boolean; +} + +export type InferSoFindIteratorResultValue = + CreateSoFindIterableOptions['resultsMapper'] extends undefined + ? SavedObjectsFindResponse + : ReturnType>['resultsMapper']>; + +/** + * Creates an `AsyncIterable` that can be used to iterate (ex. via `for..await..of`) over all the data + * matching the search query. The search request to Saved Object will use `searchAfter`, thus can iterate over + * datasets above 10k items as well. + * + * @param options + */ +export const createSoFindIterable = ({ + soClient, + findRequest: { perPage = 1000, ...findOptions }, + resultsMapper, + usePointInTime = true, +}: CreateSoFindIterableOptions): AsyncIterable< + InferSoFindIteratorResultValue +> => { + const keepAliveValue = '5m'; + let done = false; + let value: SavedObjectsFindResponse; + let searchAfterValue: SavedObjectsFindResult['sort'] | undefined; + let pointInTime: Promise<{ id: string }> = usePointInTime + ? soClient.openPointInTimeForType(findOptions.type, { keepAlive: keepAliveValue }) + : Promise.resolve({ id: '' }); + + const setValue = (findResponse: SavedObjectsFindResponse): void => { + value = resultsMapper ? resultsMapper(findResponse) : findResponse; + }; + + const setDone = async (): Promise => { + done = true; + + if (usePointInTime) { + const pitId = (await pointInTime).id; + + if (pitId) { + await soClient.closePointInTime(pitId); + } + } + }; + + const fetchData = async () => { + const findResult = await soClient + .find({ + ...findOptions, + ...(usePointInTime + ? { + pit: { + id: (await pointInTime).id, + keepAlive: keepAliveValue, + }, + } + : {}), + perPage, + searchAfter: searchAfterValue, + }) + .catch((e) => { + Error.captureStackTrace(e); + throw e; + }); + + const soItems = findResult.saved_objects; + const lastSearchHit = soItems[soItems.length - 1]; + + if (soItems.length === 0) { + setValue(findResult); + await setDone(); + return; + } + + searchAfterValue = lastSearchHit.sort; + pointInTime = Promise.resolve({ id: findResult.pit_id ?? '' }); + setValue(findResult); + + // If (for some reason) we don't have a `searchAfterValue`, + // then throw an error, or else we'll keep looping forever + if (!searchAfterValue) { + await setDone(); + throw new Error( + `Unable to store 'searchAfter' value. Last 'SavedObjectsFindResult' did not include a 'sort' property \n(did you forget to set the 'sortField' attribute on your SavedObjectsFindOptions?)':\n${JSON.stringify( + lastSearchHit + )}` + ); + } + }; + + const createIteratorResult = (): IteratorResult> => { + return { done, value }; + }; + + return { + [Symbol.asyncIterator]() { + return { + async next() { + if (!done) { + await fetchData(); + } + + return createIteratorResult(); + }, + + async return() { + done = true; + return createIteratorResult(); + }, + }; + }, + }; +}; diff --git a/x-pack/plugins/fleet/server/types/models/output.test.ts b/x-pack/plugins/fleet/server/types/models/output.test.ts index 06edd900fec2a..9c850766b9ba8 100644 --- a/x-pack/plugins/fleet/server/types/models/output.test.ts +++ b/x-pack/plugins/fleet/server/types/models/output.test.ts @@ -13,6 +13,10 @@ describe('Output model', () => { expect(validateLogstashHost('test.fr:5044')).toBeUndefined(); }); + it('should support valid host with uppercase letters', () => { + expect(validateLogstashHost('tEsT.fr:5044')).toBeUndefined(); + }); + it('should return an error for an invalid host', () => { expect(validateLogstashHost('!@#%&!#!@')).toMatchInlineSnapshot(`"Invalid Logstash host"`); }); diff --git a/x-pack/plugins/fleet/server/types/models/output.ts b/x-pack/plugins/fleet/server/types/models/output.ts index 730ec512f5a0f..765018f3ac88f 100644 --- a/x-pack/plugins/fleet/server/types/models/output.ts +++ b/x-pack/plugins/fleet/server/types/models/output.ts @@ -26,7 +26,7 @@ export function validateLogstashHost(val: string) { try { const url = new URL(`http://${val}`); - if (url.host !== val) { + if (url.host !== val.toLowerCase()) { return 'Invalid host'; } } catch (err) { diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.helpers.ts index c8e58aa6875fc..a56d633217f96 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.helpers.ts @@ -100,9 +100,9 @@ const createActions = (testBed: TestBed) => { find('closeDetailsButton').simulate('click'); }; - const toggleViewItem = (view: 'managed' | 'cloudManaged' | 'system') => { + const toggleViewItem = (view: 'managed' | 'deprecated' | 'cloudManaged' | 'system') => { const { find, component } = testBed; - const views = ['managed', 'cloudManaged', 'system']; + const views = ['managed', 'deprecated', 'cloudManaged', 'system']; // First open the pop over act(() => { diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.test.ts index be09ed36af8d3..f052317513194 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.test.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.test.ts @@ -111,6 +111,13 @@ describe('Index Templates tab', () => { type: 'system', }); + const deprecatedTemplate = fixtures.getTemplate({ + name: `.d${getRandomString()}`, + indexPatterns: ['template7Pattern1*'], + type: 'system', + deprecated: true, + }); + const template4 = fixtures.getTemplate({ name: `a${getRandomString()}`, indexPatterns: ['template4Pattern1*', 'template4Pattern2'], @@ -140,7 +147,7 @@ describe('Index Templates tab', () => { type: 'system', }); - const templates = [template1, template2, template3]; + const templates = [template1, template2, template3, deprecatedTemplate]; const legacyTemplates = [template4, template5, template6]; beforeEach(async () => { @@ -245,6 +252,35 @@ describe('Index Templates tab', () => { expect(updatedRows.length).toEqual(legacyTemplates.length); }); + test('should have a switch to view deprecated templates', async () => { + const { table, actions } = testBed; + const { tableCellsValues } = table.getMetaData('templateTable'); + + // None of the available templates should have the deprecated template + tableCellsValues.forEach((row) => { + expect( + removeWhiteSpaceOnArrayValues(row).every( + (cell) => !cell.includes(deprecatedTemplate.name) + ) + ).toBeTruthy(); + }); + + actions.toggleViewItem('system'); + actions.toggleViewItem('deprecated'); + + // After when all the tempaltes are available should have the deprecated template + const { tableCellsValues: updatedTableCellsValues } = table.getMetaData('templateTable'); + + // Find the row that has the deprecated template + const tableCellsWithDeprecatedTemplate = updatedTableCellsValues.filter((row) => { + return removeWhiteSpaceOnArrayValues(row).some((cell) => + cell.includes(deprecatedTemplate.name) + ); + }); + // Assert that it has one row with the deprecated template + expect(tableCellsWithDeprecatedTemplate.length).toBe(1); + }); + test('each row should have a link to the template details panel', async () => { const { find, exists, actions, component } = testBed; diff --git a/x-pack/plugins/index_management/common/lib/component_template_serialization.test.ts b/x-pack/plugins/index_management/common/lib/component_template_serialization.test.ts index 981aa86ea1515..84af60a41e2f9 100644 --- a/x-pack/plugins/index_management/common/lib/component_template_serialization.test.ts +++ b/x-pack/plugins/index_management/common/lib/component_template_serialization.test.ts @@ -63,6 +63,7 @@ describe('Component template serialization', () => { ] ) ).toEqual({ + isDeprecated: false, name: 'my_component_template', version: 1, _meta: { diff --git a/x-pack/plugins/index_management/common/lib/component_template_serialization.ts b/x-pack/plugins/index_management/common/lib/component_template_serialization.ts index c9a272d2e98ee..bffa1a2ad2242 100644 --- a/x-pack/plugins/index_management/common/lib/component_template_serialization.ts +++ b/x-pack/plugins/index_management/common/lib/component_template_serialization.ts @@ -51,7 +51,7 @@ export function deserializeComponentTemplate( indexTemplatesEs: TemplateFromEs[] ) { const { name, component_template: componentTemplate } = componentTemplateEs; - const { template, _meta, version } = componentTemplate; + const { template, _meta, version, deprecated } = componentTemplate; const indexTemplatesToUsedBy = getIndexTemplatesToUsedBy(indexTemplatesEs); @@ -60,6 +60,7 @@ export function deserializeComponentTemplate( template, version, _meta, + isDeprecated: Boolean(deprecated === true), _kbnMeta: { usedBy: indexTemplatesToUsedBy[name] || [], isManaged: Boolean(_meta?.managed === true), @@ -74,13 +75,14 @@ export function deserializeComponentTemplateList( indexTemplatesEs: TemplateFromEs[] ) { const { name, component_template: componentTemplate } = componentTemplateEs; - const { template, _meta } = componentTemplate; + const { template, _meta, deprecated } = componentTemplate; const indexTemplatesToUsedBy = getIndexTemplatesToUsedBy(indexTemplatesEs); const componentTemplateListItem: ComponentTemplateListItem = { name, usedBy: indexTemplatesToUsedBy[name] || [], + isDeprecated: Boolean(deprecated === true), isManaged: Boolean(_meta?.managed === true), hasSettings: hasEntries(template.settings), hasMappings: hasEntries(template.mappings), diff --git a/x-pack/plugins/index_management/common/lib/template_serialization.ts b/x-pack/plugins/index_management/common/lib/template_serialization.ts index 8a38b40258dba..5ec150a85aa17 100644 --- a/x-pack/plugins/index_management/common/lib/template_serialization.ts +++ b/x-pack/plugins/index_management/common/lib/template_serialization.ts @@ -53,6 +53,7 @@ export function deserializeTemplate( _meta, composed_of: composedOf, data_stream: dataStream, + deprecated, allow_auto_create: allowAutoCreate, } = templateEs; const { settings } = template; @@ -78,6 +79,7 @@ export function deserializeTemplate( dataStream, allowAutoCreate, _meta, + deprecated, _kbnMeta: { type, hasDatastream: Boolean(dataStream), diff --git a/x-pack/plugins/index_management/common/types/component_templates.ts b/x-pack/plugins/index_management/common/types/component_templates.ts index 68c58aefc9d06..8eb39dec1da91 100644 --- a/x-pack/plugins/index_management/common/types/component_templates.ts +++ b/x-pack/plugins/index_management/common/types/component_templates.ts @@ -18,12 +18,14 @@ export interface ComponentTemplateSerialized { lifecycle?: DataStream['lifecycle']; }; version?: number; + deprecated?: boolean; _meta?: { [key: string]: any }; lifecycle?: DataRetention; } export interface ComponentTemplateDeserialized extends ComponentTemplateSerialized { name: string; + isDeprecated?: boolean; _kbnMeta: { usedBy: string[]; isManaged: boolean; @@ -42,6 +44,7 @@ export interface ComponentTemplateListItem { hasAliases: boolean; hasSettings: boolean; isManaged: boolean; + isDeprecated?: boolean; } export interface ComponentTemplateDatastreams { diff --git a/x-pack/plugins/index_management/common/types/templates.ts b/x-pack/plugins/index_management/common/types/templates.ts index 7d85bb4d20ae0..756d631202445 100644 --- a/x-pack/plugins/index_management/common/types/templates.ts +++ b/x-pack/plugins/index_management/common/types/templates.ts @@ -21,6 +21,7 @@ export interface TemplateSerialized { mappings?: Mappings; lifecycle?: DataStream['lifecycle']; }; + deprecated?: boolean; composed_of?: string[]; version?: number; priority?: number; @@ -51,6 +52,7 @@ export interface TemplateDeserialized { ilmPolicy?: { name: string; }; + deprecated?: boolean; _meta?: { [key: string]: any }; // Composable template only // Composable template only dataStream?: { @@ -85,6 +87,7 @@ export interface TemplateListItem { hasSettings: boolean; hasAliases: boolean; hasMappings: boolean; + deprecated?: boolean; ilmPolicy?: { name: string; }; @@ -106,6 +109,7 @@ export interface LegacyTemplateSerialized { version?: number; settings?: IndexSettings; aliases?: Aliases; + deprecated?: boolean; mappings?: Mappings; order?: number; } diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_details.test.ts b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_details.test.ts index 501d14042def3..203f162bf3a47 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_details.test.ts +++ b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_details.test.ts @@ -15,6 +15,7 @@ const { setup } = pageHelpers.componentTemplateDetails; const COMPONENT_TEMPLATE: ComponentTemplateDeserialized = { name: 'comp-1', + isDeprecated: true, template: { mappings: { properties: { ip_address: { type: 'ip' } } }, aliases: { mydata: {} }, @@ -62,6 +63,9 @@ describe('', () => { // Verify footer does not display since "actions" prop was not provided expect(exists('footer')).toBe(false); + // Verify the deprecated badge is displayed + expect(exists('deprecatedComponentTemplateBadge')).toBe(true); + // Verify tabs exist expect(exists('settingsTab')).toBe(true); expect(exists('mappingsTab')).toBe(true); diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_edit.test.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_edit.test.tsx index 7c6fa35fbec4f..d113c66302ecd 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_edit.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_edit.test.tsx @@ -74,6 +74,7 @@ describe('', () => { const COMPONENT_TEMPLATE_NAME = 'comp-1'; const COMPONENT_TEMPLATE_TO_EDIT = { name: COMPONENT_TEMPLATE_NAME, + isDeprecated: true, template: { settings: { number_of_shards: 1 }, }, @@ -106,6 +107,7 @@ describe('', () => { const { exists, find } = testBed; expect(exists('pageTitle')).toBe(true); + expect(exists('deprecatedTemplateCallout')).toBe(true); expect(find('pageTitle').text()).toEqual( `Edit component template '${COMPONENT_TEMPLATE_NAME}'` ); diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_list.test.ts b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_list.test.ts index d0325dfb4a177..c4595998bcd20 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_list.test.ts +++ b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_list.test.ts @@ -43,6 +43,7 @@ describe('', () => { hasSettings: true, usedBy: [], isManaged: false, + isDeprecated: false, }; const componentTemplate2: ComponentTemplateListItem = { @@ -52,6 +53,7 @@ describe('', () => { hasSettings: true, usedBy: ['test_index_template_1'], isManaged: false, + isDeprecated: false, }; const componentTemplate3: ComponentTemplateListItem = { @@ -61,6 +63,7 @@ describe('', () => { hasSettings: true, usedBy: ['test_index_template_1', 'test_index_template_2'], isManaged: false, + isDeprecated: true, }; const componentTemplates = [componentTemplate1, componentTemplate2, componentTemplate3]; @@ -89,7 +92,7 @@ describe('', () => { const { tableCellsValues: ascTableCellsValues } = table.getMetaData('componentTemplatesTable'); const ascUsageCountValues = ascTableCellsValues.map((row) => row[2]); - expect(ascUsageCountValues).toEqual(['Not in use', '1', '2']); + expect(ascUsageCountValues).toEqual(['Not in use', '1']); // Sort descending await actions.clickTableColumnSortButton(1); @@ -97,7 +100,24 @@ describe('', () => { const { tableCellsValues: descTableCellsValues } = table.getMetaData('componentTemplatesTable'); const descUsageCountValues = descTableCellsValues.map((row) => row[2]); - expect(descUsageCountValues).toEqual(['2', '1', 'Not in use']); + expect(descUsageCountValues).toEqual(['1', 'Not in use']); + }); + + test('Hides deprecated component templates by default', async () => { + const { component, find } = testBed; + + // Initially the switch is off so we should not see any deprecated component templates + let deprecatedList = find('deprecatedComponentTemplateBadge'); + expect(deprecatedList.length).toBe(0); + + testBed.find('componentTemplatesFiltersButton').simulate('click'); + testBed.find('componentTemplates--deprecatedFilter').simulate('click'); + + component.update(); + + // Now we should see all deprecated component templates + deprecatedList = find('deprecatedComponentTemplateBadge'); + expect(deprecatedList.length).toBe(1); }); test('should reload the component templates data', async () => { diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_details.helpers.ts b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_details.helpers.ts index 44a78e0d0666f..98d9003dc307d 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_details.helpers.ts +++ b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_details.helpers.ts @@ -86,6 +86,7 @@ export type ComponentTemplateDetailsTestSubjects = | 'noMappingsCallout' | 'settingsTabContent' | 'noSettingsCallout' + | 'deprecatedComponentTemplateBadge' | 'manageComponentTemplateButton' | 'manageComponentTemplateContextMenu' | 'manageComponentTemplateContextMenu.action'; diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_form.helpers.ts b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_form.helpers.ts index d809bb230ffaa..03aff69a548af 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_form.helpers.ts +++ b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_form.helpers.ts @@ -183,6 +183,7 @@ export type ComponentTemplateFormTestSubjects = | 'stepReview.summaryTab' | 'stepReview.requestTab' | 'valueDataRetentionField' + | 'deprecatedTemplateCallout' | 'dataRetentionToggle.input' | 'versionField' | 'aliasesEditor' diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_list.helpers.ts b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_list.helpers.ts index a7087b5b45100..1bd0152c86d40 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_list.helpers.ts +++ b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_list.helpers.ts @@ -104,4 +104,7 @@ export type ComponentTemplateTestSubjects = | 'sectionLoading' | 'componentTemplatesLoadError' | 'deleteComponentTemplateButton' - | 'reloadButton'; + | 'deprecatedComponentTemplateBadge' + | 'reloadButton' + | 'componentTemplatesFiltersButton' + | 'componentTemplates--deprecatedFilter'; diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_details/component_template_details.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_details/component_template_details.tsx index 51d98ea2ea1c8..3f053b9aa3fd0 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_details/component_template_details.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_details/component_template_details.tsx @@ -29,6 +29,7 @@ import { attemptToURIDecode, } from '../shared_imports'; import { useComponentTemplatesContext } from '../component_templates_context'; +import { DeprecatedBadge } from '../components'; import { TabSummary } from './tab_summary'; import { ComponentTemplateTabs, TabType } from './tabs'; import { ManageButton, ManageAction } from './manage_button'; @@ -120,6 +121,9 @@ export const ComponentTemplateDetailsFlyoutContent: React.FunctionComponent @@ -132,7 +136,14 @@ export const ComponentTemplateDetailsFlyoutContent: React.FunctionComponent - {componentTemplateDetails?._kbnMeta.isManaged ? ( + {isDeprecated && ( + + {' '} + + + )} + + {isManaged && ( {' '} @@ -142,7 +153,7 @@ export const ComponentTemplateDetailsFlyoutContent: React.FunctionComponent - ) : null} + )} diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/table.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/table.tsx index b8b371f6e4e01..f4d9c55407fd9 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/table.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/table.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { FunctionComponent, useState } from 'react'; +import React, { FunctionComponent, useState, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { METRIC_TYPE } from '@kbn/analytics'; import { FormattedMessage } from '@kbn/i18n-react'; @@ -17,12 +17,37 @@ import { EuiIcon, EuiLink, EuiBadge, + EuiPopover, + EuiFilterGroup, + EuiSelectable, + EuiFilterButton, + EuiSelectableOption, } from '@elastic/eui'; import { ScopedHistory } from '@kbn/core/public'; import { ComponentTemplateListItem, reactRouterNavigate } from '../shared_imports'; import { UIM_COMPONENT_TEMPLATE_DETAILS } from '../constants'; import { useComponentTemplatesContext } from '../component_templates_context'; +import { DeprecatedBadge } from '../components'; + +const inUseFilterLabel = i18n.translate( + 'xpack.idxMgmt.componentTemplatesList.table.inUseFilterLabel', + { + defaultMessage: 'In use', + } +); +const managedFilterLabel = i18n.translate( + 'xpack.idxMgmt.componentTemplatesList.table.managedFilterLabel', + { + defaultMessage: 'Managed', + } +); +const deprecatedFilterLabel = i18n.translate( + 'xpack.idxMgmt.componentTemplatesList.table.deprecatedFilterLabel', + { + defaultMessage: 'Deprecated', + } +); export interface Props { componentTemplates: ComponentTemplateListItem[]; @@ -43,8 +68,64 @@ export const ComponentTable: FunctionComponent = ({ }) => { const { trackMetric } = useComponentTemplatesContext(); + // By default, we want to show all the component templates that are not deprecated. + const [filterOptions, setFilterOptions] = useState([ + { key: 'inUse', label: inUseFilterLabel, 'data-test-subj': 'componentTemplates--inUseFilter' }, + { + key: 'managed', + label: managedFilterLabel, + 'data-test-subj': 'componentTemplates--managedFilter', + }, + { + key: 'deprecated', + label: deprecatedFilterLabel, + 'data-test-subj': 'componentTemplates--deprecatedFilter', + checked: 'off', + }, + ]); + const [selection, setSelection] = useState([]); + const filteredComponentTemplates = useMemo(() => { + const inUseFilter = filterOptions.find(({ key }) => key === 'inUse')?.checked; + const managedFilter = filterOptions.find(({ key }) => key === 'managed')?.checked; + const deprecatedFilter = filterOptions.find(({ key }) => key === 'deprecated')?.checked; + return (componentTemplates || []).filter((component) => { + return !( + (deprecatedFilter === 'off' && component.isDeprecated) || + (deprecatedFilter === 'on' && !component.isDeprecated) || + (managedFilter === 'off' && component.isManaged) || + (managedFilter === 'on' && !component.isManaged) || + (inUseFilter === 'off' && component.usedBy.length >= 1) || + (inUseFilter === 'on' && component.usedBy.length === 0) + ); + }); + }, [componentTemplates, filterOptions]); + + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const onButtonClick = () => { + setIsPopoverOpen(!isPopoverOpen); + }; + const closePopover = () => { + setIsPopoverOpen(false); + }; + const button = ( + item.checked !== 'off').length} + hasActiveFilters={!!filterOptions.find((item) => item.checked === 'on')} + numActiveFilters={filterOptions.filter((item) => item.checked === 'on').length} + > + {i18n.translate('xpack.idxMgmt.componentTemplatesList.table.filtersButtonLabel', { + defaultMessage: 'Filters', + })} + + ); + const tableProps: EuiInMemoryTableProps = { tableLayout: 'auto', itemId: 'name', @@ -110,37 +191,33 @@ export const ComponentTable: FunctionComponent = ({ }, filters: [ { - type: 'is', - field: 'isManaged', - name: i18n.translate('xpack.idxMgmt.componentTemplatesList.table.isManagedFilterLabel', { - defaultMessage: 'Managed', - }), - }, - { - type: 'field_value_toggle_group', - field: 'usedBy.length', - items: [ - { - value: 1, - name: i18n.translate( - 'xpack.idxMgmt.componentTemplatesList.table.inUseFilterOptionLabel', - { - defaultMessage: 'In use', - } - ), - operator: 'gte', - }, - { - value: 0, - name: i18n.translate( - 'xpack.idxMgmt.componentTemplatesList.table.notInUseFilterOptionLabel', - { - defaultMessage: 'Not in use', - } - ), - operator: 'eq', - }, - ], + type: 'custom_component', + component: () => { + return ( + + + + {(list) =>
{list}
} +
+
+
+ ); + }, }, ], }, @@ -170,6 +247,12 @@ export const ComponentTable: FunctionComponent = ({ > {name} + {item.isDeprecated && ( + <> +   + + + )} {item.isManaged && ( <> {' '} @@ -294,7 +377,7 @@ export const ComponentTable: FunctionComponent = ({ ], }, ], - items: componentTemplates ?? [], + items: filteredComponentTemplates, }; return ; diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_edit/component_template_edit.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_edit/component_template_edit.tsx index 334a8295d9c2e..220acb8121212 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_edit/component_template_edit.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_edit/component_template_edit.tsx @@ -8,7 +8,7 @@ import React, { useState, useEffect, useMemo } from 'react'; import { RouteComponentProps } from 'react-router-dom'; import { FormattedMessage } from '@kbn/i18n-react'; -import { EuiPageSection, EuiPageHeader, EuiSpacer } from '@elastic/eui'; +import { EuiPageSection, EuiPageHeader, EuiSpacer, EuiCallOut } from '@elastic/eui'; import { breadcrumbService, IndexManagementBreadcrumb } from '../../../../services/breadcrumbs'; import { useComponentTemplatesContext } from '../../component_templates_context'; @@ -125,6 +125,28 @@ export const ComponentTemplateEdit: React.FunctionComponent + {componentTemplate?.isDeprecated && ( + <> + + } + iconType="warning" + color="warning" + data-test-subj="deprecatedTemplateCallout" + > + + + + + )} + { + return ( + + + {i18n.translate('xpack.idxMgmt.componentTemplate.deprecatedTemplateBadgeText', { + defaultMessage: 'Deprecated', + })} + + + ); +}; diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/components/index.ts b/x-pack/plugins/index_management/public/application/components/component_templates/components/index.ts new file mode 100644 index 0000000000000..463da26756d70 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/components/component_templates/components/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 { DeprecatedBadge } from './deprecated_badge'; diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/components/index.ts b/x-pack/plugins/index_management/public/application/sections/home/template_list/components/index.ts index 8b756be535ed2..af90df8cd113c 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/template_list/components/index.ts +++ b/x-pack/plugins/index_management/public/application/sections/home/template_list/components/index.ts @@ -6,3 +6,4 @@ */ export { TemplateTypeIndicator } from './template_type_indicator'; +export { TemplateDeprecatedBadge } from './template_deprecated_badge'; diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/components/template_deprecated_badge.tsx b/x-pack/plugins/index_management/public/application/sections/home/template_list/components/template_deprecated_badge.tsx new file mode 100644 index 0000000000000..bc8a2416d155b --- /dev/null +++ b/x-pack/plugins/index_management/public/application/sections/home/template_list/components/template_deprecated_badge.tsx @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiToolTip, EuiBadge } from '@elastic/eui'; + +export const TemplateDeprecatedBadge = () => { + return ( + + + {i18n.translate('xpack.idxMgmt.templateList.table.deprecatedTemplateBadgeText', { + defaultMessage: 'Deprecated', + })} + + + ); +}; diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/template_details_content.tsx b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/template_details_content.tsx index 4f00409d2c3cf..d2156d1aa958e 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/template_details_content.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/template_details_content.tsx @@ -38,7 +38,7 @@ import { TemplateDeleteModal, SectionError, Error } from '../../../../components import { useLoadIndexTemplate } from '../../../../services/api'; import { useServices } from '../../../../app_context'; import { TabAliases, TabMappings, TabSettings } from '../../../../components/shared'; -import { TemplateTypeIndicator } from '../components'; +import { TemplateTypeIndicator, TemplateDeprecatedBadge } from '../components'; import { TabSummary, TabPreview } from './tabs'; const SUMMARY_TAB_ID = 'summary'; @@ -120,6 +120,12 @@ export const TemplateDetailsContent = ({ {templateName} {templateDetails && ( <> + {templateDetails.deprecated && ( + <> +   + + + )}   diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_list.tsx b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_list.tsx index 53292944cf158..322d2075adb60 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_list.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_list.tsx @@ -48,16 +48,22 @@ import { TemplateTable } from './template_table'; import { TemplateDetails } from './template_details'; import { LegacyTemplateTable } from './legacy_templates/template_table'; -type FilterName = 'managed' | 'cloudManaged' | 'system'; +type FilterName = 'managed' | 'deprecated' | 'cloudManaged' | 'system'; interface MatchParams { templateName?: string; } function filterTemplates(templates: TemplateListItem[], types: string[]): TemplateListItem[] { return templates.filter((template) => { + // Exclude deprecated templates by default, unless 'deprecated' is specified in types + if (template.deprecated && !types.includes('deprecated')) { + return false; + } + if (template._kbnMeta.type === 'default') { return true; } + return types.includes(template._kbnMeta.type); }); } @@ -92,6 +98,12 @@ export const TemplateList: React.FunctionComponent = ({ > {name} {' '} + {item.deprecated && ( + <> +   + + + )} +   ); diff --git a/x-pack/plugins/index_management/public/application/sections/template_edit/template_edit.tsx b/x-pack/plugins/index_management/public/application/sections/template_edit/template_edit.tsx index 9c68d602e4e02..3dd8cac943c0e 100644 --- a/x-pack/plugins/index_management/public/application/sections/template_edit/template_edit.tsx +++ b/x-pack/plugins/index_management/public/application/sections/template_edit/template_edit.tsx @@ -70,6 +70,7 @@ export const TemplateEdit: React.FunctionComponent )} + {isDeprecatedTemplate && ( + <> + + } + iconType="warning" + color="warning" + data-test-subj="deprecatedIndexTemplateCallout" + > + + + + + )} { + describe('sorts by name', () => { + const indices = [{ name: 'test1' }, { name: 'test2' }] as Index[]; + it('ascending', () => { + const sorted = sortTable(indices, 'name', true); + expect(sorted).toEqual([{ name: 'test1' }, { name: 'test2' }]); + }); + it('descending', () => { + const sorted = sortTable(indices, 'name', false); + expect(sorted).toEqual([{ name: 'test2' }, { name: 'test1' }]); + }); + }); + + describe('sorts by status', () => { + const indices = [{ status: 'open' }, { status: 'close' }] as Index[]; + it('ascending', () => { + const sorted = sortTable(indices, 'status', true); + expect(sorted).toEqual([{ status: 'close' }, { status: 'open' }]); + }); + it('descending', () => { + const sorted = sortTable(indices, 'status', false); + expect(sorted).toEqual([{ status: 'open' }, { status: 'close' }]); + }); + }); + + describe('sorts by health', () => { + const indices = [{ health: 'green' }, { health: 'yellow' }, { health: 'red' }] as Index[]; + it('ascending', () => { + const sorted = sortTable(indices, 'health', true); + expect(sorted).toEqual([{ health: 'green' }, { health: 'red' }, { health: 'yellow' }]); + }); + it('descending', () => { + const sorted = sortTable(indices, 'health', false); + expect(sorted).toEqual([{ health: 'yellow' }, { health: 'red' }, { health: 'green' }]); + }); + }); + + describe('sorts by primary', () => { + const indices = [{ primary: '1' }, { primary: '12' }, { primary: '2' }] as Index[]; + it('ascending', () => { + const sorted = sortTable(indices, 'primary', true); + expect(sorted).toEqual([{ primary: '1' }, { primary: '2' }, { primary: '12' }]); + }); + it('descending', () => { + const sorted = sortTable(indices, 'primary', false); + expect(sorted).toEqual([{ primary: '12' }, { primary: '2' }, { primary: '1' }]); + }); + }); + + describe('sorts by replica', () => { + const indices = [{ replica: '1' }, { replica: '12' }, { replica: '2' }] as Index[]; + it('ascending', () => { + const sorted = sortTable(indices, 'replica', true); + expect(sorted).toEqual([{ replica: '1' }, { replica: '2' }, { replica: '12' }]); + }); + it('descending', () => { + const sorted = sortTable(indices, 'replica', false); + expect(sorted).toEqual([{ replica: '12' }, { replica: '2' }, { replica: '1' }]); + }); + }); + + describe('sorts by documents', () => { + const indices = [{ documents: 1 }, { documents: 12 }, { documents: 2 }] as Index[]; + it('ascending', () => { + const sorted = sortTable(indices, 'documents', true); + expect(sorted).toEqual([{ documents: 1 }, { documents: 2 }, { documents: 12 }]); + }); + it('descending', () => { + const sorted = sortTable(indices, 'documents', false); + expect(sorted).toEqual([{ documents: 12 }, { documents: 2 }, { documents: 1 }]); + }); + }); + + describe('sorts by size', () => { + const indices = [{ size: '248b' }, { size: '2.35mb' }, { size: '6.36kb' }] as Index[]; + it('ascending', () => { + const sorted = sortTable(indices, 'size', true); + expect(sorted).toEqual([{ size: '248b' }, { size: '6.36kb' }, { size: '2.35mb' }]); + }); + it('descending', () => { + const sorted = sortTable(indices, 'size', false); + expect(sorted).toEqual([{ size: '2.35mb' }, { size: '6.36kb' }, { size: '248b' }]); + }); + }); + + describe('sorts by primary_size', () => { + const indices = [ + { primary_size: '248b' }, + { primary_size: '2.35mb' }, + { primary_size: '6.36kb' }, + ] as Index[]; + it('ascending', () => { + const sorted = sortTable(indices, 'primary_size', true); + expect(sorted).toEqual([ + { primary_size: '248b' }, + { primary_size: '6.36kb' }, + { primary_size: '2.35mb' }, + ]); + }); + it('descending', () => { + const sorted = sortTable(indices, 'primary_size', false); + expect(sorted).toEqual([ + { primary_size: '2.35mb' }, + { primary_size: '6.36kb' }, + { primary_size: '248b' }, + ]); + }); + }); + + describe('sorts by data_stream', () => { + const indices = [ + { data_stream: 'test1' }, + { data_stream: undefined }, + { data_stream: 'test2' }, + ] as Index[]; + it('ascending', () => { + const sorted = sortTable(indices, 'data_stream', true); + expect(sorted).toEqual([ + { data_stream: 'test1' }, + { data_stream: 'test2' }, + { data_stream: undefined }, + ]); + }); + it('descending', () => { + const sorted = sortTable(indices, 'data_stream', false); + expect(sorted).toEqual([ + { data_stream: undefined }, + { data_stream: 'test2' }, + { data_stream: 'test1' }, + ]); + }); + }); + + describe('sorts by a column added via extensions service', () => { + const indices = [ + { ilm: { phase: 'hot' } }, + { ilm: { phase: 'warm' } }, + { ilm: { phase: undefined } }, + ] as Index[]; + const extensionsService = { + columns: [ + { + fieldName: 'ilm.phase', + label: 'ILM phase', + order: 10, + render: (index: Index) => (index.ilm?.managed ? index.ilm.phase : ''), + sort: (index: Index) => (index.ilm?.managed ? index.ilm.phase : ''), + }, + ], + } as ExtensionsService; + it('ascending', () => { + const sorted = sortTable(indices, 'ilm.phase', true, extensionsService); + expect(sorted).toEqual([ + { ilm: { phase: 'hot' } }, + { ilm: { phase: 'warm' } }, + { ilm: { phase: undefined } }, + ]); + }); + it('descending', () => { + const sorted = sortTable(indices, 'ilm.phase', false, extensionsService); + expect(sorted).toEqual([ + { ilm: { phase: undefined } }, + { ilm: { phase: 'warm' } }, + { ilm: { phase: 'hot' } }, + ]); + }); + }); + describe('sorting by a column without a sorter', () => { + const indices = [ + { test: 'test1' }, + { test: 'test2' }, + { test: undefined }, + { test: 'test5' }, + { test: 'test3' }, + { test: undefined }, + ] as unknown as Index[]; + it('ascending', () => { + const sorted = sortTable(indices, 'test', true); + expect(sorted).toEqual([ + { test: 'test1' }, + { test: 'test2' }, + { test: 'test3' }, + { test: 'test5' }, + { test: undefined }, + { test: undefined }, + ]); + }); + it('descending', () => { + const sorted = sortTable(indices, 'test', false); + expect(sorted).toEqual([ + { test: undefined }, + { test: undefined }, + { test: 'test5' }, + { test: 'test3' }, + { test: 'test2' }, + { test: 'test1' }, + ]); + }); + }); +}); diff --git a/x-pack/plugins/index_management/public/application/services/sort_table.ts b/x-pack/plugins/index_management/public/application/services/sort_table.ts index 92257391e335a..f06cd1535838d 100644 --- a/x-pack/plugins/index_management/public/application/services/sort_table.ts +++ b/x-pack/plugins/index_management/public/application/services/sort_table.ts @@ -5,7 +5,9 @@ * 2.0. */ -import { sortBy } from 'lodash'; +import { sortBy, get } from 'lodash'; +import { Index } from '../../../common'; +import type { ExtensionsService } from '../../services'; type SortField = | 'name' @@ -28,39 +30,57 @@ const unitMagnitude = { pb: 5, }; -const stringSort = (fieldName: SortField) => (item: { [key: string]: any }) => { - return item[fieldName]; -}; +type SortFunction = (index: Index) => any; -const numericSort = (fieldName: SortField) => (item: { [key: string]: any }) => +item[fieldName]; +const numericSort = + (fieldName: SortField): SortFunction => + (item) => + Number(item[fieldName]); -const byteSort = (fieldName: SortField) => (item: { [key: string]: any }) => { - const rawValue = item[fieldName]; - // raw value can be missing if index is closed - if (!rawValue) { - return 0; - } - const matchResult = rawValue.match(/(.*)([kmgtp]b)/); - if (!matchResult) { - return 0; - } - const [, number, unit]: [string, string, Unit] = matchResult; - return +number * Math.pow(1024, unitMagnitude[unit]); -}; +const byteSort = + (fieldName: SortField): SortFunction => + (item) => { + const rawValue = String(item[fieldName]); + // raw value can be missing if index is closed + if (!rawValue) { + return 0; + } + const matchResult = rawValue.match(/(.*)([kmgtp]b)/); + if (!matchResult) { + return 0; + } + const [, number, unit] = matchResult; + return +number * Math.pow(1024, unitMagnitude[unit as Unit]); + }; -const sorters = { - name: stringSort('name'), - status: stringSort('status'), - health: stringSort('health'), - primary: numericSort('primary'), - replica: numericSort('replica'), - documents: numericSort('documents'), - size: byteSort('size'), - primary_size: byteSort('primary_size'), - data_stream: stringSort('data_stream'), +const getSorters = (extensionsService?: ExtensionsService) => { + const sorters = { + primary: numericSort('primary'), + replica: numericSort('replica'), + documents: numericSort('documents'), + size: byteSort('size'), + primary_size: byteSort('primary_size'), + } as any; + const columns = extensionsService?.columns ?? []; + for (const column of columns) { + if (column.sort) { + sorters[column.fieldName] = column.sort; + } + } + return sorters; }; -export const sortTable = (array = [], sortField: SortField, isSortAscending: boolean) => { - const sorted = sortBy(array, sorters[sortField]); +export const sortTable = ( + array: Index[] = [], + sortField: SortField | string, + isSortAscending: boolean, + extensionsService?: ExtensionsService +) => { + const sorters = getSorters(extensionsService); + let sorter = sorters[sortField]; + if (!sorter) { + sorter = (index: Index) => get(index, sortField); + } + const sorted = sortBy(array, sorter); return isSortAscending ? sorted : sorted.reverse(); }; diff --git a/x-pack/plugins/index_management/public/application/store/selectors/index.js b/x-pack/plugins/index_management/public/application/store/selectors/index.js index 6ed09d8234fa4..2e1e01f3122ae 100644 --- a/x-pack/plugins/index_management/public/application/store/selectors/index.js +++ b/x-pack/plugins/index_management/public/application/store/selectors/index.js @@ -94,7 +94,8 @@ export const getPageOfIndices = createSelector( const sortedIndexes = sortTable( filteredIndices, tableState.sortField, - tableState.isSortAscending + tableState.isSortAscending, + extensionsService ); const { firstItemIndex, lastItemIndex } = pager; const pagedIndexes = sortedIndexes.slice(firstItemIndex, lastItemIndex + 1); diff --git a/x-pack/plugins/index_management/public/services/extensions_service.ts b/x-pack/plugins/index_management/public/services/extensions_service.ts index 3e8f6118bf44e..e7cb4aef2282d 100644 --- a/x-pack/plugins/index_management/public/services/extensions_service.ts +++ b/x-pack/plugins/index_management/public/services/extensions_service.ts @@ -44,6 +44,8 @@ export interface IndicesListColumn { label: string; order: number; render?: (index: Index) => ReactNode; + // return a value used for sorting (only if the value is different from the original value at index[fieldName]) + sort?: (index: Index) => any; } export interface ExtensionsSetup { diff --git a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_rule_type.ts b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_rule_type.ts index 8ea6baff28240..d1fbef7a7095e 100644 --- a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_rule_type.ts +++ b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_rule_type.ts @@ -91,24 +91,32 @@ export async function registerInventoryThresholdRuleType( return; } + const paramsSchema = schema.object( + { + criteria: schema.arrayOf(condition), + nodeType: schema.string() as Type, + filterQuery: schema.maybe( + schema.string({ validate: validateIsStringElasticsearchJSONFilter }) + ), + sourceId: schema.string(), + alertOnNoData: schema.maybe(schema.boolean()), + }, + { unknowns: 'allow' } + ); + alertingPlugin.registerType({ id: METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID, name: i18n.translate('xpack.infra.metrics.inventory.alertName', { defaultMessage: 'Inventory', }), validate: { - params: schema.object( - { - criteria: schema.arrayOf(condition), - nodeType: schema.string() as Type, - filterQuery: schema.maybe( - schema.string({ validate: validateIsStringElasticsearchJSONFilter }) - ), - sourceId: schema.string(), - alertOnNoData: schema.maybe(schema.boolean()), - }, - { unknowns: 'allow' } - ), + params: paramsSchema, + }, + schemas: { + params: { + type: 'config-schema', + schema: paramsSchema, + }, }, defaultActionGroupId: FIRED_ACTIONS_ID, doesSetRecoveryContext: true, 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 4de8e8675b433..475862664c336 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 @@ -4,13 +4,12 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { i18n } from '@kbn/i18n'; import { getIndexPatternFromSQLQuery, getIndexPatternFromESQLQuery } from '@kbn/esql-utils'; -import type { AggregateQuery, Query, Filter } from '@kbn/es-query'; +import type { AggregateQuery } from '@kbn/es-query'; import { getESQLAdHocDataview } from '@kbn/esql-utils'; +import { getLensAttributesFromSuggestion } from '@kbn/visualization-utils'; import { fetchFieldsFromESQL } from '@kbn/text-based-editor'; -import type { DataView, DataViewSpec } from '@kbn/data-views-plugin/public'; -import type { Suggestion } from '../../../types'; +import type { DataViewSpec } from '@kbn/data-views-plugin/public'; import type { TypedLensByValueInput } from '../../../embeddable/embeddable_component'; import type { LensPluginStartDependencies } from '../../../plugin'; import type { DatasourceMap, VisualizationMap } from '../../../types'; @@ -72,68 +71,15 @@ export const getSuggestions = async ( const firstSuggestion = allSuggestions[0]; - const attrs = getLensAttributes({ + const attrs = getLensAttributesFromSuggestion({ filters: [], query, suggestion: firstSuggestion, dataView, - }); + }) as TypedLensByValueInput['attributes']; return attrs; } catch (e) { setErrors([e]); } return undefined; }; - -export const getLensAttributes = ({ - filters, - query, - suggestion, - dataView, -}: { - filters: Filter[]; - query: Query | AggregateQuery; - suggestion: Suggestion | undefined; - dataView?: DataView; -}) => { - const suggestionDatasourceState = Object.assign({}, suggestion?.datasourceState); - const suggestionVisualizationState = Object.assign({}, suggestion?.visualizationState); - const datasourceStates = - suggestion && suggestion.datasourceState - ? { - [suggestion.datasourceId!]: { - ...suggestionDatasourceState, - }, - } - : { - formBased: {}, - }; - const visualization = suggestionVisualizationState; - const attributes = { - title: suggestion - ? suggestion.title - : i18n.translate('xpack.lens.config.suggestion.title', { - defaultMessage: 'New suggestion', - }), - references: [ - { - id: dataView?.id ?? '', - name: `textBasedLanguages-datasource-layer-suggestion`, - type: 'index-pattern', - }, - ], - state: { - datasourceStates, - filters, - query, - visualization, - ...(dataView && - dataView.id && - !dataView.isPersisted() && { - adHocDataViews: { [dataView.id]: dataView.toSpec(false) }, - }), - }, - visualizationType: suggestion ? suggestion.visualizationId : 'lnsXY', - } as TypedLensByValueInput['attributes']; - return attributes; -}; diff --git a/x-pack/plugins/lens/public/datasources/form_based/dimension_panel/time_shift.tsx b/x-pack/plugins/lens/public/datasources/form_based/dimension_panel/time_shift.tsx index 8bf26114416de..fe69c9d76d45b 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/dimension_panel/time_shift.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/dimension_panel/time_shift.tsx @@ -155,7 +155,7 @@ export function TimeShift({ ); })} selectedOptions={getSelectedOption()} - singleSelection={{ asPlainText: true }} + singleSelection={{ asPlainText: false }} isInvalid={isLocalValueInvalid} onCreateOption={(val) => { const parsedVal = parseTimeShift(val); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx index f7b6443d98766..d9c0438d6d298 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx @@ -170,7 +170,8 @@ describe('LayerPanel', () => { }); }); - describe('single group', () => { + // FLAKY: https://github.com/elastic/kibana/issues/176247 + describe.skip('single group', () => { it('should render the group with a way to add a new column', async () => { mockVisualization.getConfiguration.mockReturnValue({ groups: [defaultGroup], diff --git a/x-pack/plugins/lens/public/embeddable/expression_wrapper.tsx b/x-pack/plugins/lens/public/embeddable/expression_wrapper.tsx index d28d6fd3b527e..8540058683c11 100644 --- a/x-pack/plugins/lens/public/embeddable/expression_wrapper.tsx +++ b/x-pack/plugins/lens/public/embeddable/expression_wrapper.tsx @@ -46,7 +46,6 @@ export interface ExpressionWrapperProps { executionContext?: KibanaExecutionContext; lensInspector: LensInspector; noPadding?: boolean; - shouldUseSizeTransitionVeil?: boolean; abortController?: AbortController; } @@ -73,7 +72,6 @@ export function ExpressionWrapper({ executionContext, lensInspector, noPadding, - shouldUseSizeTransitionVeil, abortController, }: ExpressionWrapperProps) { if (!expression) return null; @@ -97,7 +95,6 @@ export function ExpressionWrapper({ syncCursor={syncCursor} executionContext={executionContext} abortController={abortController} - shouldUseSizeTransitionVeil={shouldUseSizeTransitionVeil ?? true} renderError={(errorMessage, error) => { const messages = getOriginalRequestErrorMessages(error || null); addUserMessages(messages); diff --git a/x-pack/plugins/lens/public/lens_attribute_service.ts b/x-pack/plugins/lens/public/lens_attribute_service.ts index c63d9c1dd5c3d..69b76669695fe 100644 --- a/x-pack/plugins/lens/public/lens_attribute_service.ts +++ b/x-pack/plugins/lens/public/lens_attribute_service.ts @@ -8,7 +8,9 @@ import type { CoreStart } from '@kbn/core/public'; import type { AttributeService } from '@kbn/embeddable-plugin/public'; import { OnSaveProps } from '@kbn/saved-objects-plugin/public'; +import { SavedObjectCommon } from '@kbn/saved-objects-finder-plugin/common'; import type { LensPluginStartDependencies } from './plugin'; +import type { LensSavedObjectAttributes as LensSavedObjectAttributesWithoutReferences } from '../common/content_management'; import type { LensSavedObjectAttributes, LensByValueInput, @@ -26,6 +28,16 @@ export type LensAttributeService = AttributeService< LensUnwrapMetaInfo >; +export const savedObjectToEmbeddableAttributes = ( + savedObject: SavedObjectCommon +): LensSavedObjectAttributes => { + return { + ...savedObject.attributes, + state: savedObject.attributes.state as LensSavedObjectAttributes['state'], + references: savedObject.references, + }; +}; + export function getLensAttributeService( core: CoreStart, startDependencies: LensPluginStartDependencies @@ -51,11 +63,7 @@ export function getLensAttributeService( item: savedObject, meta: { outcome, aliasTargetId, aliasPurpose }, } = await savedObjectStore.load(savedObjectId); - const { attributes, references, id } = savedObject; - const document = { - ...attributes, - references, - }; + const { id } = savedObject; const sharingSavedObjectProps = { aliasTargetId, @@ -65,10 +73,7 @@ export function getLensAttributeService( }; return { - attributes: { - ...document, - state: document.state as LensSavedObjectAttributes['state'], - }, + attributes: savedObjectToEmbeddableAttributes(savedObject), metaInfo: { sharingSavedObjectProps, managed: savedObject.managed, diff --git a/x-pack/plugins/lens/public/plugin.ts b/x-pack/plugins/lens/public/plugin.ts index 61ffb1d0686d0..663a5d8b574c2 100644 --- a/x-pack/plugins/lens/public/plugin.ts +++ b/x-pack/plugins/lens/public/plugin.ts @@ -64,6 +64,7 @@ import { } from '@kbn/content-management-plugin/public'; import { i18n } from '@kbn/i18n'; import type { ServerlessPluginStart } from '@kbn/serverless/public'; +import { registerSavedObjectToPanelMethod } from '@kbn/embeddable-plugin/public'; import type { EditorFrameService as EditorFrameServiceType } from './editor_frame_service'; import type { FormBasedDatasource as FormBasedDatasourceType, @@ -117,7 +118,7 @@ import { visualizeTSVBAction } from './trigger_actions/visualize_tsvb_actions'; import { visualizeAggBasedVisAction } from './trigger_actions/visualize_agg_based_vis_actions'; import { visualizeDashboardVisualizePanelction } from './trigger_actions/dashboard_visualize_panel_actions'; -import type { LensEmbeddableInput } from './embeddable'; +import type { LensByValueInput, LensEmbeddableInput } from './embeddable'; import { EmbeddableFactory, LensEmbeddableStartServices } from './embeddable/embeddable_factory'; import { EmbeddableComponent, getEmbeddableComponent } from './embeddable/embeddable_component'; import { getSaveModalComponent } from './app_plugin/shared/saved_modal_lazy'; @@ -130,8 +131,13 @@ import { ChartInfoApi } from './chart_info_api'; import { type LensAppLocator, LensAppLocatorDefinition } from '../common/locator/locator'; import { downloadCsvShareProvider } from './app_plugin/csv_download_provider/csv_download_provider'; -import { CONTENT_ID, LATEST_VERSION } from '../common/content_management'; +import { + CONTENT_ID, + LATEST_VERSION, + LensSavedObjectAttributes, +} from '../common/content_management'; import type { EditLensConfigurationProps } from './app_plugin/shared/edit_on_the_fly/get_edit_lens_configuration'; +import { savedObjectToEmbeddableAttributes } from './lens_attribute_service'; export type { SaveProps } from './app_plugin'; @@ -424,6 +430,21 @@ export class LensPlugin { () => startServices().plugins.data.nowProvider.get() ); + registerSavedObjectToPanelMethod( + CONTENT_ID, + (savedObject) => { + if (!savedObject.managed) { + return { savedObjectId: savedObject.id }; + } + + const panel = { + attributes: savedObjectToEmbeddableAttributes(savedObject), + }; + + return panel; + } + ); + const getPresentationUtilContext = () => startServices().plugins.presentationUtil.ContextProvider; diff --git a/x-pack/plugins/lens/public/shared_components/palette_picker.tsx b/x-pack/plugins/lens/public/shared_components/palette_picker.tsx index 51977e551128e..6796eee37a542 100644 --- a/x-pack/plugins/lens/public/shared_components/palette_picker.tsx +++ b/x-pack/plugins/lens/public/shared_components/palette_picker.tsx @@ -7,6 +7,7 @@ import React from 'react'; import type { PaletteOutput, PaletteRegistry } from '@kbn/coloring'; +import { getActivePaletteName } from '@kbn/coloring'; import { EuiColorPalettePicker, EuiColorPalettePickerPaletteProps } from '@elastic/eui'; import { EuiFormRow } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -20,6 +21,8 @@ export function PalettePicker({ activePalette?: PaletteOutput; setPalette: (palette: PaletteOutput) => void; }) { + const paletteName = getActivePaletteName(activePalette?.name); + const palettesToShow: EuiColorPalettePickerPaletteProps[] = palettes .getAll() .filter(({ internal }) => !internal) @@ -28,10 +31,7 @@ export function PalettePicker({ value: id, title, type: 'fixed', - palette: getCategoricalColors( - 10, - id === activePalette?.name ? activePalette?.params : undefined - ), + palette: getCategoricalColors(10, id === paletteName ? activePalette?.params : undefined), }; }); return ( @@ -51,7 +51,7 @@ export function PalettePicker({ name: newPalette, }); }} - valueOfSelected={activePalette?.name || 'default'} + valueOfSelected={paletteName} selectionDisplay={'palette'} /> diff --git a/x-pack/plugins/lens/public/trigger_actions/open_lens_config/create_action.test.tsx b/x-pack/plugins/lens/public/trigger_actions/open_lens_config/create_action.test.tsx index 9a06fea94cd98..27372f10ce973 100644 --- a/x-pack/plugins/lens/public/trigger_actions/open_lens_config/create_action.test.tsx +++ b/x-pack/plugins/lens/public/trigger_actions/open_lens_config/create_action.test.tsx @@ -8,16 +8,20 @@ import type { CoreStart } from '@kbn/core/public'; import { coreMock } from '@kbn/core/public/mocks'; import type { LensPluginStartDependencies } from '../../plugin'; import { createMockStartDependencies } from '../../editor_frame_service/mocks'; +import { getMockPresentationContainer } from '@kbn/presentation-containers/mocks'; import { CreateESQLPanelAction } from './create_action'; describe('create Lens panel action', () => { const core = coreMock.createStart(); const mockStartDependencies = createMockStartDependencies() as unknown as LensPluginStartDependencies; + const mockPresentationContainer = getMockPresentationContainer(); describe('compatibility check', () => { it('is incompatible if ui setting for ES|QL is off', async () => { const configurablePanelAction = new CreateESQLPanelAction(mockStartDependencies, core); - const isCompatible = await configurablePanelAction.isCompatible(); + const isCompatible = await configurablePanelAction.isCompatible({ + embeddable: mockPresentationContainer, + }); expect(isCompatible).toBeFalsy(); }); @@ -33,7 +37,9 @@ describe('create Lens panel action', () => { }, } as CoreStart; const createESQLAction = new CreateESQLPanelAction(mockStartDependencies, updatedCore); - const isCompatible = await createESQLAction.isCompatible(); + const isCompatible = await createESQLAction.isCompatible({ + embeddable: mockPresentationContainer, + }); expect(isCompatible).toBeTruthy(); }); diff --git a/x-pack/plugins/lens/public/trigger_actions/open_lens_config/create_action.tsx b/x-pack/plugins/lens/public/trigger_actions/open_lens_config/create_action.tsx index aa33a629c3969..07301f2394130 100644 --- a/x-pack/plugins/lens/public/trigger_actions/open_lens_config/create_action.tsx +++ b/x-pack/plugins/lens/public/trigger_actions/open_lens_config/create_action.tsx @@ -6,29 +6,16 @@ */ import { i18n } from '@kbn/i18n'; import type { CoreStart } from '@kbn/core/public'; -import { Action } from '@kbn/ui-actions-plugin/public'; -import type { - EmbeddableFactory, - EmbeddableInput, - IEmbeddable, -} from '@kbn/embeddable-plugin/public'; +import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public'; +import { EmbeddableApiContext } from '@kbn/presentation-publishing'; +import { apiIsPresentationContainer } from '@kbn/presentation-containers'; import type { LensPluginStartDependencies } from '../../plugin'; const ACTION_CREATE_ESQL_CHART = 'ACTION_CREATE_ESQL_CHART'; -interface Context { - createNewEmbeddable: ( - embeddableFactory: EmbeddableFactory, - initialInput?: Partial, - dismissNotification?: boolean - ) => Promise; - deleteEmbeddable: (embeddableId: string) => void; - initialInput?: Partial; -} - export const getAsyncHelpers = async () => await import('../../async_services'); -export class CreateESQLPanelAction implements Action { +export class CreateESQLPanelAction implements Action { public type = ACTION_CREATE_ESQL_CHART; public id = ACTION_CREATE_ESQL_CHART; public order = 50; @@ -49,19 +36,20 @@ export class CreateESQLPanelAction implements Action { return 'esqlVis'; } - public async isCompatible() { + public async isCompatible({ embeddable }: EmbeddableApiContext) { + if (!apiIsPresentationContainer(embeddable)) return false; // compatible only when ES|QL advanced setting is enabled const { isCreateActionCompatible } = await getAsyncHelpers(); return isCreateActionCompatible(this.core); } - public async execute({ createNewEmbeddable, deleteEmbeddable }: Context) { + public async execute({ embeddable }: EmbeddableApiContext) { + if (!apiIsPresentationContainer(embeddable)) throw new IncompatibleActionError(); const { executeCreateAction } = await getAsyncHelpers(); executeCreateAction({ deps: this.startDependencies, core: this.core, - createNewEmbeddable, - deleteEmbeddable, + api: embeddable, }); } } 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 7fb732cd72ccf..49ce940743e70 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 @@ -6,20 +6,17 @@ */ import { createGetterSetter } from '@kbn/kibana-utils-plugin/common'; import type { CoreStart } from '@kbn/core/public'; -import type { - EmbeddableFactory, - EmbeddableInput, - IEmbeddable, -} from '@kbn/embeddable-plugin/public'; +import { getLensAttributesFromSuggestion } from '@kbn/visualization-utils'; import { IncompatibleActionError } from '@kbn/ui-actions-plugin/public'; +import { PresentationContainer } from '@kbn/presentation-containers'; import { getESQLAdHocDataview, getIndexForESQLQuery } from '@kbn/esql-utils'; import type { Datasource, Visualization } from '../../types'; import type { LensPluginStartDependencies } from '../../plugin'; import { fetchDataFromAggregateQuery } from '../../datasources/text_based/fetch_data_from_aggregate_query'; import { suggestionsApi } from '../../lens_suggestions_api'; -import { getLensAttributes } from '../../app_plugin/shared/edit_on_the_fly/helpers'; import { generateId } from '../../id_generator'; import { executeEditAction } from './edit_action_helpers'; +import { Embeddable } from '../../embeddable'; // datasourceMap and visualizationMap setters/getters export const [getVisualizationMap, setVisualizationMap] = createGetterSetter< @@ -37,17 +34,11 @@ export function isCreateActionCompatible(core: CoreStart) { export async function executeCreateAction({ deps, core, - createNewEmbeddable, - deleteEmbeddable, + api, }: { deps: LensPluginStartDependencies; core: CoreStart; - createNewEmbeddable: ( - embeddableFactory: EmbeddableFactory, - initialInput?: Partial, - dismissNotification?: boolean - ) => Promise; - deleteEmbeddable: (embeddableId: string) => void; + api: PresentationContainer; }) { const isCompatibleAction = isCreateActionCompatible(core); const defaultDataView = await deps.dataViews.getDefaultDataView({ @@ -103,27 +94,24 @@ export async function executeCreateAction({ // Lens might not return suggestions for some cases, i.e. in case of errors if (!allSuggestions.length) return undefined; const [firstSuggestion] = allSuggestions; - const attrs = getLensAttributes({ + const attrs = getLensAttributesFromSuggestion({ filters: [], query: defaultEsqlQuery, suggestion: firstSuggestion, dataView, }); - const input = { - attributes: attrs, - id: generateId(), - }; - const embeddableStart = deps.embeddable; - const factory = embeddableStart.getEmbeddableFactory('lens'); - if (!factory) { - return undefined; - } - const embeddable = await createNewEmbeddable(factory, input, true); + const embeddable = await api.addNewPanel({ + panelType: 'lens', + initialState: { + attributes: attrs, + id: generateId(), + }, + }); // open the flyout if embeddable has been created successfully if (embeddable) { const deletePanel = () => { - deleteEmbeddable(embeddable.id); + api.removePanel(embeddable.id); }; executeEditAction({ diff --git a/x-pack/plugins/lens/tsconfig.json b/x-pack/plugins/lens/tsconfig.json index 779aa6886b05e..73883bc849e27 100644 --- a/x-pack/plugins/lens/tsconfig.json +++ b/x-pack/plugins/lens/tsconfig.json @@ -109,7 +109,8 @@ "@kbn/text-based-editor", "@kbn/managed-content-badge", "@kbn/sort-predicates", - "@kbn/presentation-publishing" + "@kbn/presentation-publishing", + "@kbn/saved-objects-finder-plugin" ], "exclude": ["target/**/*"] } diff --git a/x-pack/plugins/lists/server/routes/list/import_list_item_route.ts b/x-pack/plugins/lists/server/routes/list/import_list_item_route.ts index 822a18fd13cf1..9e64fe59404c6 100644 --- a/x-pack/plugins/lists/server/routes/list/import_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/list/import_list_item_route.ts @@ -5,6 +5,8 @@ * 2.0. */ +import { extname } from 'path'; + import { schema } from '@kbn/config-schema'; import { validate } from '@kbn/securitysolution-io-ts-utils'; import { transformError } from '@kbn/securitysolution-es-utils'; @@ -17,6 +19,8 @@ import { buildRouteValidation, buildSiemResponse } from '../utils'; import { createStreamFromBuffer } from '../utils/create_stream_from_buffer'; import { getListClient } from '..'; +const validFileExtensions = ['.csv', '.txt']; + export const importListItemRoute = (router: ListsPluginRouter, config: ConfigType): void => { router.versioned .post({ @@ -47,10 +51,29 @@ export const importListItemRoute = (router: ListsPluginRouter, config: ConfigTyp async (context, request, response) => { const siemResponse = buildSiemResponse(response); try { - const stream = createStreamFromBuffer(request.body); const { deserializer, list_id: listId, serializer, type } = request.query; const lists = await getListClient(context); + const filename = await lists.getImportFilename({ + stream: createStreamFromBuffer(request.body), + }); + if (!filename) { + return siemResponse.error({ + body: 'To import a list item, the file name must be specified', + statusCode: 400, + }); + } + const fileExtension = extname(filename).toLowerCase(); + if (!validFileExtensions.includes(fileExtension)) { + return siemResponse.error({ + body: `Unsupported media type. File must be one of the following types: [${validFileExtensions.join( + ', ' + )}]`, + statusCode: 415, + }); + } + + const stream = createStreamFromBuffer(request.body); const listDataExists = await lists.getListDataStreamExists(); if (!listDataExists) { const listIndexExists = await lists.getListIndexExists(); diff --git a/x-pack/plugins/lists/server/services/lists/list_client.ts b/x-pack/plugins/lists/server/services/lists/list_client.ts index 826e4a545d80d..d1d5c585c3f85 100644 --- a/x-pack/plugins/lists/server/services/lists/list_client.ts +++ b/x-pack/plugins/lists/server/services/lists/list_client.ts @@ -37,6 +37,7 @@ import type { import type { ConfigType } from '../../config'; import { + BufferLines, createListItem, deleteListItem, deleteListItemByValue, @@ -69,6 +70,7 @@ import type { FindAllListItemsOptions, FindListItemOptions, FindListOptions, + GetImportFilename, GetListItemByValueOptions, GetListItemOptions, GetListItemsByValueOptions, @@ -715,6 +717,33 @@ export class ListClient { }); }; + /** + * Gets the filename of the imported file + * @param options + * @param options.stream The stream to pull the import from + * @returns + */ + public getImportFilename = ({ stream }: GetImportFilename): Promise => { + return new Promise((resolve, reject) => { + const { config } = this; + const readBuffer = new BufferLines({ bufferSize: config.importBufferSize, input: stream }); + let fileName: string | undefined; + readBuffer.on('fileName', async (fileNameEmitted: string) => { + try { + readBuffer.pause(); + fileName = decodeURIComponent(fileNameEmitted); + readBuffer.resume(); + } catch (err) { + reject(err); + } + }); + + readBuffer.on('close', () => { + resolve(fileName); + }); + }); + }; + /** * Imports list items to a stream. If the list already exists, this will append the list items to the existing list. * If the list does not exist, this will auto-create the list and then add the items to that list. diff --git a/x-pack/plugins/lists/server/services/lists/list_client_types.ts b/x-pack/plugins/lists/server/services/lists/list_client_types.ts index 7509eeb914241..4c64e8e940163 100644 --- a/x-pack/plugins/lists/server/services/lists/list_client_types.ts +++ b/x-pack/plugins/lists/server/services/lists/list_client_types.ts @@ -335,3 +335,12 @@ export interface SearchListItemByValuesOptions { /** The value to search for list items based off. */ value: unknown[]; } + +/** + * ListClient.getImportFilename + * {@link ListClient.getImportFilename} + */ +export interface GetImportFilename { + /** The stream to pull the import from */ + stream: Readable; +} diff --git a/x-pack/plugins/maps/public/classes/sources/esql_source/convert_to_geojson.ts b/x-pack/plugins/maps/public/classes/sources/esql_source/convert_to_geojson.ts index 3940cd9102c54..a446f976b5677 100644 --- a/x-pack/plugins/maps/public/classes/sources/esql_source/convert_to_geojson.ts +++ b/x-pack/plugins/maps/public/classes/sources/esql_source/convert_to_geojson.ts @@ -9,14 +9,19 @@ import { parse } from 'wellknown'; import { Feature, FeatureCollection, GeoJsonProperties } from 'geojson'; import type { ESQLSearchReponse } from '@kbn/es-types'; -import { getGeometryColumnIndex } from './esql_utils'; +import { EMPTY_FEATURE_COLLECTION } from '../../../../common/constants'; +import { isGeometryColumn } from './esql_utils'; export function convertToGeoJson(resp: ESQLSearchReponse): FeatureCollection { - const geometryIndex = getGeometryColumnIndex(resp.columns); + const geometryColumnIndex = resp.columns.findIndex(isGeometryColumn); + if (geometryColumnIndex === -1) { + return EMPTY_FEATURE_COLLECTION; + } + const features: Feature[] = []; for (let i = 0; i < resp.values.length; i++) { const hit = resp.values[i]; - const wkt = hit[geometryIndex]; + const wkt = hit[geometryColumnIndex]; if (!wkt) { continue; } @@ -25,7 +30,7 @@ export function convertToGeoJson(resp: ESQLSearchReponse): FeatureCollection { const properties: GeoJsonProperties = {}; for (let j = 0; j < hit.length; j++) { // do not store geometry in properties - if (j === geometryIndex) { + if (j === geometryColumnIndex) { continue; } properties[resp.columns[j].name] = hit[j] as unknown; diff --git a/x-pack/plugins/maps/public/classes/sources/esql_source/esql_source.tsx b/x-pack/plugins/maps/public/classes/sources/esql_source/esql_source.tsx index be5cdea7c7fbf..d438a714beb40 100644 --- a/x-pack/plugins/maps/public/classes/sources/esql_source/esql_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/esql_source/esql_source.tsx @@ -13,8 +13,8 @@ import { v4 as uuidv4 } from 'uuid'; import { Adapters } from '@kbn/inspector-plugin/common/adapters'; import { getIndexPatternFromESQLQuery, getLimitFromESQLQuery } from '@kbn/esql-utils'; import { buildEsQuery } from '@kbn/es-query'; -import type { BoolQuery, Filter, Query } from '@kbn/es-query'; -import type { ESQLSearchReponse } from '@kbn/es-types'; +import type { Filter, Query } from '@kbn/es-query'; +import type { ESQLSearchParams, ESQLSearchReponse } from '@kbn/es-types'; import { getEsQueryConfig } from '@kbn/data-service/src/es_query'; import { getTime } from '@kbn/data-plugin/public'; import { FIELD_ORIGIN, SOURCE_TYPES, VECTOR_SHAPE_TYPE } from '../../../../common/constants'; @@ -32,12 +32,7 @@ import type { IField } from '../../fields/field'; import { InlineField } from '../../fields/inline_field'; import { getData, getUiSettings } from '../../../kibana_services'; import { convertToGeoJson } from './convert_to_geojson'; -import { - getFieldType, - getGeometryColumnIndex, - ESQL_GEO_POINT_TYPE, - ESQL_GEO_SHAPE_TYPE, -} from './esql_utils'; +import { getFieldType, isGeometryColumn, ESQL_GEO_SHAPE_TYPE } from './esql_utils'; import { UpdateSourceEditor } from './update_source_editor'; type ESQLSourceSyncMeta = Pick< @@ -128,16 +123,8 @@ export class ESQLSource extends AbstractVectorSource implements IVectorSource { } async getSupportedShapeTypes() { - let geomtryColumnType = ESQL_GEO_POINT_TYPE; - try { - const index = getGeometryColumnIndex(this._descriptor.columns); - if (index > -1) { - geomtryColumnType = this._descriptor.columns[index].type; - } - } catch (error) { - // errors for missing geometry columns surfaced in UI by data loading - } - return geomtryColumnType === ESQL_GEO_SHAPE_TYPE + const index = this._descriptor.columns.findIndex(isGeometryColumn); + return index !== -1 && this._descriptor.columns[index].type === ESQL_GEO_SHAPE_TYPE ? [VECTOR_SHAPE_TYPE.POINT, VECTOR_SHAPE_TYPE.LINE, VECTOR_SHAPE_TYPE.POLYGON] : [VECTOR_SHAPE_TYPE.POINT]; } @@ -154,8 +141,9 @@ export class ESQLSource extends AbstractVectorSource implements IVectorSource { inspectorAdapters: Adapters ): Promise { const limit = getLimitFromESQLQuery(this._descriptor.esql); - const params: { query: string; filter?: { bool: BoolQuery } } = { + const params: ESQLSearchParams = { query: this._descriptor.esql, + dropNullColumns: true, }; const query: Query[] = []; diff --git a/x-pack/plugins/maps/public/classes/sources/esql_source/esql_utils.ts b/x-pack/plugins/maps/public/classes/sources/esql_source/esql_utils.ts index 7a4b5048c820a..c247170874ba3 100644 --- a/x-pack/plugins/maps/public/classes/sources/esql_source/esql_utils.ts +++ b/x-pack/plugins/maps/public/classes/sources/esql_source/esql_utils.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { lastValueFrom } from 'rxjs'; import { getIndexPatternFromESQLQuery } from '@kbn/esql-utils'; -import type { ESQLColumn } from '@kbn/es-types'; +import type { ESQLColumn, ESQLSearchReponse } from '@kbn/es-types'; import { ES_GEO_FIELD_TYPE } from '../../../../common/constants'; import { getData, getIndexPatternService } from '../../../kibana_services'; @@ -22,13 +22,6 @@ export const ESQL_GEO_POINT_TYPE = 'geo_point'; // ESQL_GEO_SHAPE_TYPE is a column type from an ESQL response export const ESQL_GEO_SHAPE_TYPE = 'geo_shape'; -const NO_GEOMETRY_COLUMN_ERROR_MSG = i18n.translate( - 'xpack.maps.source.esql.noGeometryColumnErrorMsg', - { - defaultMessage: 'Elasticsearch ES|QL query does not have a geometry column.', - } -); - export function isGeometryColumn(column: ESQLColumn) { return [ESQL_GEO_POINT_TYPE, ESQL_GEO_SHAPE_TYPE].includes(column.type); } @@ -36,7 +29,11 @@ export function isGeometryColumn(column: ESQLColumn) { export function verifyGeometryColumn(columns: ESQLColumn[]) { const geometryColumns = columns.filter(isGeometryColumn); if (geometryColumns.length === 0) { - throw new Error(NO_GEOMETRY_COLUMN_ERROR_MSG); + throw new Error( + i18n.translate('xpack.maps.source.esql.noGeometryColumnErrorMsg', { + defaultMessage: 'Elasticsearch ES|QL query does not have a geometry column.', + }) + ); } if (geometryColumns.length > 1) { @@ -51,14 +48,6 @@ export function verifyGeometryColumn(columns: ESQLColumn[]) { } } -export function getGeometryColumnIndex(columns: ESQLColumn[]) { - const index = columns.findIndex(isGeometryColumn); - if (index === -1) { - throw new Error(NO_GEOMETRY_COLUMN_ERROR_MSG); - } - return index; -} - export async function getESQLMeta(esql: string) { const fields = await getFields(esql); return { @@ -104,7 +93,8 @@ async function getColumns(esql: string) { ) ); - return (resp.rawResponse as unknown as { columns: ESQLColumn[] }).columns; + const searchResponse = resp.rawResponse as unknown as ESQLSearchReponse; + return searchResponse.all_columns ? searchResponse.all_columns : searchResponse.columns; } catch (error) { throw new Error( i18n.translate('xpack.maps.source.esql.getColumnsErrorMsg', { diff --git a/x-pack/plugins/maps/public/map_attribute_service.ts b/x-pack/plugins/maps/public/map_attribute_service.ts index 6cc226b72dc4b..5f07f3954eecd 100644 --- a/x-pack/plugins/maps/public/map_attribute_service.ts +++ b/x-pack/plugins/maps/public/map_attribute_service.ts @@ -9,6 +9,7 @@ import { SavedObjectReference } from '@kbn/core/types'; import type { ResolvedSimpleSavedObject } from '@kbn/core/public'; import { AttributeService } from '@kbn/embeddable-plugin/public'; import type { OnSaveProps } from '@kbn/saved-objects-plugin/public'; +import { SavedObjectCommon } from '@kbn/saved-objects-finder-plugin/common'; import type { MapAttributes } from '../common/content_management'; import { MAP_EMBEDDABLE_NAME, MAP_SAVED_OBJECT_TYPE } from '../common/constants'; import { getCoreOverlays, getEmbeddableService } from './kibana_services'; @@ -39,6 +40,17 @@ export type MapAttributeService = AttributeService< MapUnwrapMetaInfo >; +export const savedObjectToEmbeddableAttributes = ( + savedObject: SavedObjectCommon +) => { + const { attributes } = injectReferences(savedObject); + + return { + ...attributes, + references: savedObject.references, + }; +}; + let mapAttributeService: MapAttributeService | null = null; export function getMapAttributeService(): MapAttributeService { @@ -90,12 +102,8 @@ export function getMapAttributeService(): MapAttributeService { throw savedObject.error; } - const { attributes } = injectReferences(savedObject); return { - attributes: { - ...attributes, - references: savedObject.references, - }, + attributes: savedObjectToEmbeddableAttributes(savedObject), metaInfo: { sharingSavedObjectProps: { aliasTargetId, diff --git a/x-pack/plugins/maps/public/plugin.ts b/x-pack/plugins/maps/public/plugin.ts index 4e654f9e5c641..351f49fcfd077 100644 --- a/x-pack/plugins/maps/public/plugin.ts +++ b/x-pack/plugins/maps/public/plugin.ts @@ -25,7 +25,11 @@ import type { HomePublicPluginSetup } from '@kbn/home-plugin/public'; import type { VisualizationsSetup, VisualizationsStart } from '@kbn/visualizations-plugin/public'; import type { Plugin as ExpressionsPublicPlugin } from '@kbn/expressions-plugin/public'; import { VISUALIZE_GEO_FIELD_TRIGGER } from '@kbn/ui-actions-plugin/public'; -import type { EmbeddableSetup, EmbeddableStart } from '@kbn/embeddable-plugin/public'; +import { + EmbeddableSetup, + EmbeddableStart, + registerSavedObjectToPanelMethod, +} from '@kbn/embeddable-plugin/public'; import { CONTEXT_MENU_TRIGGER } from '@kbn/embeddable-plugin/public'; import type { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/public'; import type { MapsEmsPluginPublicStart } from '@kbn/maps-ems-plugin/public'; @@ -82,7 +86,9 @@ import { MapInspectorView } from './inspector/map_adapter/map_inspector_view'; import { VectorTileInspectorView } from './inspector/vector_tile_adapter/vector_tile_inspector_view'; import { setupLensChoroplethChart } from './lens'; -import { CONTENT_ID, LATEST_VERSION } from '../common/content_management'; +import { CONTENT_ID, LATEST_VERSION, MapAttributes } from '../common/content_management'; +import { savedObjectToEmbeddableAttributes } from './map_attribute_service'; +import { MapByValueInput } from './embeddable'; export interface MapsPluginSetupDependencies { cloud?: CloudSetup; @@ -214,6 +220,16 @@ export class MapsPlugin name: APP_NAME, }); + registerSavedObjectToPanelMethod(CONTENT_ID, (savedObject) => { + if (!savedObject.managed) { + return { savedObjectId: savedObject.id }; + } + + return { + attributes: savedObjectToEmbeddableAttributes(savedObject), + }; + }); + setupLensChoroplethChart(core, plugins.expressions, plugins.lens); // register wrapper around legacy tile_map and region_map visualizations diff --git a/x-pack/plugins/maps/tsconfig.json b/x-pack/plugins/maps/tsconfig.json index 8dcb5e3db7a26..27cdf35bfb949 100644 --- a/x-pack/plugins/maps/tsconfig.json +++ b/x-pack/plugins/maps/tsconfig.json @@ -85,6 +85,7 @@ "@kbn/code-editor", "@kbn/managed-content-badge", "@kbn/presentation-publishing", + "@kbn/saved-objects-finder-plugin", "@kbn/esql-utils", ], "exclude": [ diff --git a/x-pack/plugins/ml/public/application/components/anomalies_table/anomalies_table.js b/x-pack/plugins/ml/public/application/components/anomalies_table/anomalies_table.js index 9b023ae640c10..0edb008184aae 100644 --- a/x-pack/plugins/ml/public/application/components/anomalies_table/anomalies_table.js +++ b/x-pack/plugins/ml/public/application/components/anomalies_table/anomalies_table.js @@ -144,9 +144,8 @@ export class AnomaliesTableInternal extends Component { }; unsetShowRuleEditorFlyoutFunction = () => { - const showRuleEditorFlyout = () => {}; this.setState({ - showRuleEditorFlyout, + showRuleEditorFlyout: () => {}, }); }; diff --git a/x-pack/plugins/ml/public/application/components/rule_editor/rule_editor_flyout.js b/x-pack/plugins/ml/public/application/components/rule_editor/rule_editor_flyout.js index 0e21de91dbbb9..abb7055b41a8f 100644 --- a/x-pack/plugins/ml/public/application/components/rule_editor/rule_editor_flyout.js +++ b/x-pack/plugins/ml/public/application/components/rule_editor/rule_editor_flyout.js @@ -83,11 +83,13 @@ class RuleEditorFlyoutUI extends Component { } componentDidMount() { - this.toastNotificationService = toastNotificationServiceProvider( - this.props.kibana.services.notifications.toasts - ); - if (typeof this.props.setShowFunction === 'function') { - this.props.setShowFunction(this.showFlyout); + if (this.props.kibana.services.notifications) { + this.toastNotificationService = toastNotificationServiceProvider( + this.props.kibana.services.notifications.toasts + ); + if (typeof this.props.setShowFunction === 'function') { + this.props.setShowFunction(this.showFlyout); + } } } @@ -480,7 +482,7 @@ class RuleEditorFlyoutUI extends Component { }; render() { - const docsUrl = this.props.kibana.services.docLinks.links.ml.customRules; + const docsUrl = this.props.kibana.services.docLinks?.links.ml.customRules; const { isFlyoutVisible, job, diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/_timeseriesexplorer.scss b/x-pack/plugins/ml/public/application/timeseriesexplorer/_timeseriesexplorer.scss index 6160db3b940c8..e47e69c741a90 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/_timeseriesexplorer.scss +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/_timeseriesexplorer.scss @@ -74,6 +74,11 @@ pointer-events: none; } + .values-dots circle { + fill: $euiColorPrimary; + stroke-width: 0; + } + .metric-value { opacity: 1; fill: transparent; diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js index a66a5f0efece2..b459f0bffcee0 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js @@ -17,6 +17,8 @@ import { isEqual, reduce, each, get } from 'lodash'; import d3 from 'd3'; import moment from 'moment'; +import { EuiPopover } from '@elastic/eui'; + import { i18n } from '@kbn/i18n'; import { getFormattedSeverityScore, getSeverityWithLow } from '@kbn/ml-anomaly-utils'; import { formatHumanReadableDateTimeSeconds } from '@kbn/ml-date-utils'; @@ -52,6 +54,9 @@ import { } from './timeseries_chart_annotations'; import { MlAnnotationUpdatesContext } from '../../../contexts/ml/ml_annotation_updates_context'; +import { LinksMenuUI } from '../../../components/anomalies_table/links_menu'; +import { RuleEditorFlyout } from '../../../components/rule_editor'; + const focusZoomPanelHeight = 25; const focusChartHeight = 310; const focusHeight = focusZoomPanelHeight + focusChartHeight; @@ -60,6 +65,7 @@ const contextChartLineTopMargin = 3; const chartSpacing = 25; const swimlaneHeight = 30; const ctxAnnotationMargin = 2; +const popoverMenuOffset = 28; const annotationHeight = ANNOTATION_SYMBOL_HEIGHT + ctxAnnotationMargin * 2; const margin = { top: 10, right: 10, bottom: 15, left: 40 }; @@ -123,11 +129,18 @@ class TimeseriesChartIntl extends Component { zoomFromFocusLoaded: PropTypes.object, zoomToFocusLoaded: PropTypes.object, tooltipService: PropTypes.object.isRequired, + tableData: PropTypes.object, + sourceIndicesWithGeoFields: PropTypes.object.isRequired, }; rowMouseenterSubscriber = null; rowMouseleaveSubscriber = null; + constructor(props) { + super(props); + this.state = { popoverData: null, popoverCoords: [0, 0], showRuleEditorFlyout: () => {} }; + } + componentWillUnmount() { const element = d3.select(this.rootNode); element.html(''); @@ -206,7 +219,10 @@ class TimeseriesChartIntl extends Component { const highlightFocusChartAnomaly = this.highlightFocusChartAnomaly.bind(this); const boundHighlightFocusChartAnnotation = highlightFocusChartAnnotation.bind(this); function tableRecordMousenterListener({ record, type = 'anomaly' }) { - if (type === 'anomaly') { + // do not display tooltips if the action popover is active + if (this.state.popoverData !== null) { + return; + } else if (type === 'anomaly') { highlightFocusChartAnomaly(record); } else if (type === 'annotation') { boundHighlightFocusChartAnnotation(record); @@ -217,7 +233,7 @@ class TimeseriesChartIntl extends Component { const boundUnhighlightFocusChartAnnotation = unhighlightFocusChartAnnotation.bind(this); function tableRecordMouseleaveListener({ record, type = 'anomaly' }) { if (type === 'anomaly') { - unhighlightFocusChartAnomaly(record); + unhighlightFocusChartAnomaly(); } else { boundUnhighlightFocusChartAnnotation(record); } @@ -594,8 +610,8 @@ class TimeseriesChartIntl extends Component { const data = focusChartData; const contextYScale = this.contextYScale; + const showAnomalyPopover = this.showAnomalyPopover.bind(this); const showFocusChartTooltip = this.showFocusChartTooltip.bind(this); - const hideFocusChartTooltip = this.props.tooltipService.hide.bind(this.props.tooltipService); const focusChart = d3.select('.focus-chart'); @@ -766,6 +782,8 @@ class TimeseriesChartIntl extends Component { ) ); + const that = this; + // Remove dots that are no longer needed i.e. if number of chart points has decreased. dots.exit().remove(); // Create any new dots that are needed i.e. if number of chart points has increased. @@ -773,8 +791,16 @@ class TimeseriesChartIntl extends Component { .enter() .append('circle') .attr('r', LINE_CHART_ANOMALY_RADIUS) + .on('click', function (d) { + d3.event.preventDefault(); + if (d.anomalyScore === undefined) return; + showAnomalyPopover(d, this); + }) .on('mouseover', function (d) { - showFocusChartTooltip(d, this); + // Show the tooltip only if the actions menu isn't active + if (that.state.popoverData === null) { + showFocusChartTooltip(d, this); + } }) .on('mouseout', () => this.props.tooltipService.hide()); @@ -786,6 +812,7 @@ class TimeseriesChartIntl extends Component { .attr('cy', (d) => { return this.focusYScale(d.value); }) + .attr('data-test-subj', (d) => (d.anomalyScore !== undefined ? 'mlAnomalyMarker' : undefined)) .attr('class', (d) => { let markerClass = 'metric-value'; if (d.anomalyScore !== undefined) { @@ -810,6 +837,11 @@ class TimeseriesChartIntl extends Component { .enter() .append('path') .attr('d', d3.svg.symbol().size(MULTI_BUCKET_SYMBOL_SIZE).type('cross')) + .on('click', function (d) { + d3.event.preventDefault(); + if (d.anomalyScore === undefined) return; + showAnomalyPopover(d, this); + }) .on('mouseover', function (d) { showFocusChartTooltip(d, this); }) @@ -821,6 +853,7 @@ class TimeseriesChartIntl extends Component { 'transform', (d) => `translate(${this.focusXScale(d.date)}, ${this.focusYScale(d.value)})` ) + .attr('data-test-subj', 'mlAnomalyMarker') .attr('class', (d) => `anomaly-marker multi-bucket ${getSeverityWithLow(d.anomalyScore).id}`); // Add rectangular markers for any scheduled events. @@ -1479,6 +1512,37 @@ class TimeseriesChartIntl extends Component { this.setContextBrushExtent(new Date(from), new Date(to)); } + showAnomalyPopover(marker, circle) { + const anomalyTime = marker.date.getTime(); + + // The table items could be aggregated, so we have to find the item + // that has the closest timestamp to the selected anomaly from the chart. + const tableItem = this.props.tableData.anomalies.reduce((closestItem, currentItem) => { + const closestItemDelta = Math.abs(anomalyTime - closestItem.source.timestamp); + const currentItemDelta = Math.abs(anomalyTime - currentItem.source.timestamp); + return currentItemDelta < closestItemDelta ? currentItem : closestItem; + }, this.props.tableData.anomalies[0]); + + if (tableItem) { + // Overwrite the timestamp of the possibly aggregated table item with the + // timestamp of the anomaly clicked in the chart so we're able to pick + // the right baseline and deviation time ranges for Log Rate Analysis. + tableItem.source.timestamp = anomalyTime; + + // Calculate the relative coordinates of the clicked anomaly marker + // so we're able to position the popover actions menu above it. + const dotRect = circle.getBoundingClientRect(); + const rootRect = this.rootNode.getBoundingClientRect(); + const x = Math.round(dotRect.x + dotRect.width / 2 - rootRect.x); + const y = Math.round(dotRect.y + dotRect.height / 2 - rootRect.y) - popoverMenuOffset; + + // Hide any active tooltip + this.props.tooltipService.hide(); + // Set the popover state to enable the actions menu + this.setState({ popoverData: tableItem, popoverCoords: [x, y] }); + } + } + showFocusChartTooltip(marker, circle) { const { modelPlotEnabled } = this.props; @@ -1818,6 +1882,7 @@ class TimeseriesChartIntl extends Component { .append('path') .attr('d', d3.svg.symbol().size(MULTI_BUCKET_SYMBOL_SIZE).type('cross')) .attr('transform', (d) => `translate(${focusXScale(d.date)}, ${focusYScale(d.value)})`) + .attr('data-test-subj', 'mlAnomalyMarker') .attr( 'class', (d) => @@ -1830,6 +1895,7 @@ class TimeseriesChartIntl extends Component { .attr('r', LINE_CHART_ANOMALY_RADIUS) .attr('cx', (d) => focusXScale(d.date)) .attr('cy', (d) => focusYScale(d.value)) + .attr('data-test-subj', 'mlAnomalyMarker') .attr( 'class', (d) => @@ -1862,8 +1928,60 @@ class TimeseriesChartIntl extends Component { this.rootNode = componentNode; } + closePopover() { + this.setState({ popoverData: null, popoverCoords: [0, 0] }); + } + + setShowRuleEditorFlyoutFunction = (func) => { + this.setState({ + showRuleEditorFlyout: func, + }); + }; + + unsetShowRuleEditorFlyoutFunction = () => { + this.setState({ + showRuleEditorFlyout: () => {}, + }); + }; + render() { - return
; + return ( + <> + + {this.state.popoverData !== null && ( +
+ this.closePopover()} + panelPaddingSize="none" + anchorPosition="upLeft" + > + this.closePopover()} + sourceIndicesWithGeoFields={this.props.sourceIndicesWithGeoFields} + /> + +
+ )} +
+ + ); } } @@ -1874,6 +1992,7 @@ export const TimeseriesChart = (props) => { if (annotationProp === undefined) { return null; } + return ( { const wrapper = mountWithIntl(); - expect(wrapper.html()).toBe(`
`); + expect(wrapper.html()).toBe('
'); }); }); diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart_with_tooltip.tsx b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart_with_tooltip.tsx index af42229d8ac79..66da1e4222887 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart_with_tooltip.tsx +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart_with_tooltip.tsx @@ -8,6 +8,7 @@ import React, { FC, useEffect, useState, useCallback, useContext } from 'react'; import { i18n } from '@kbn/i18n'; import { extractErrorMessage } from '@kbn/ml-error-utils'; +import type { MlAnomaliesTableRecord } from '@kbn/ml-anomaly-utils'; import { MlTooltipComponent } from '../../../components/chart_tooltip'; import { TimeseriesChart } from './timeseries_chart'; import { CombinedJob } from '../../../../../common/types/anomaly_detection_jobs'; @@ -17,6 +18,7 @@ import { useMlKibana, useNotifications } from '../../../contexts/kibana'; import { getBoundsRoundedToInterval } from '../../../util/time_buckets'; import { getControlsForDetector } from '../../get_controls_for_detector'; import { MlAnnotationUpdatesContext } from '../../../contexts/ml/ml_annotation_updates_context'; +import { SourceIndicesWithGeoFields } from '../../../explorer/explorer_utils'; interface TimeSeriesChartWithTooltipsProps { bounds: any; @@ -30,6 +32,11 @@ interface TimeSeriesChartWithTooltipsProps { chartProps: any; lastRefresh: number; contextAggregationInterval: any; + tableData?: { + anomalies: MlAnomaliesTableRecord[]; + interval: string; + }; + sourceIndicesWithGeoFields: SourceIndicesWithGeoFields; } export const TimeSeriesChartWithTooltips: FC = ({ bounds, @@ -43,6 +50,11 @@ export const TimeSeriesChartWithTooltips: FC = chartProps, lastRefresh, contextAggregationInterval, + tableData = { + anomalies: [], + interval: 'second', + }, + sourceIndicesWithGeoFields, }) => { const { toasts: toastNotifications } = useNotifications(); const { @@ -132,6 +144,8 @@ export const TimeSeriesChartWithTooltips: FC = showForecast={showForecast} showModelBounds={showModelBounds} tooltipService={tooltipService} + tableData={tableData} + sourceIndicesWithGeoFields={sourceIndicesWithGeoFields} /> )} diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js index b7b8b7fe6e77b..757f4cb06543e 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js @@ -1218,6 +1218,8 @@ export class TimeSeriesExplorer extends React.Component { showForecast={showForecast} showModelBounds={showModelBounds} lastRefresh={lastRefresh} + tableData={tableData} + sourceIndicesWithGeoFields={sourceIndicesWithGeoFields} /> {focusAnnotationError !== undefined && ( <> @@ -1316,7 +1318,7 @@ export class TimeSeriesExplorer extends React.Component { bounds={bounds} tableData={tableData} filter={this.tableFilter} - sourceIndicesWithGeoFields={sourceIndicesWithGeoFields} + sourceIndicesWithGeoFields={this.state.sourceIndicesWithGeoFields} selectedJobs={[ { id: selectedJob.job_id, diff --git a/x-pack/plugins/ml/server/lib/alerts/register_anomaly_detection_alert_type.ts b/x-pack/plugins/ml/server/lib/alerts/register_anomaly_detection_alert_type.ts index 707486b5b8d1b..73cad085832ad 100644 --- a/x-pack/plugins/ml/server/lib/alerts/register_anomaly_detection_alert_type.ts +++ b/x-pack/plugins/ml/server/lib/alerts/register_anomaly_detection_alert_type.ts @@ -180,6 +180,12 @@ export function registerAnomalyDetectionAlertType({ validate: { params: mlAnomalyDetectionAlertParams, }, + schemas: { + params: { + type: 'config-schema', + schema: mlAnomalyDetectionAlertParams, + }, + }, actionVariables: { context: [ { diff --git a/x-pack/plugins/ml/server/lib/alerts/register_jobs_monitoring_rule_type.ts b/x-pack/plugins/ml/server/lib/alerts/register_jobs_monitoring_rule_type.ts index 014c3dd365b6d..3c3432db7ab42 100644 --- a/x-pack/plugins/ml/server/lib/alerts/register_jobs_monitoring_rule_type.ts +++ b/x-pack/plugins/ml/server/lib/alerts/register_jobs_monitoring_rule_type.ts @@ -122,6 +122,12 @@ export function registerJobsMonitoringRuleType({ validate: { params: anomalyDetectionJobsHealthRuleParams, }, + schemas: { + params: { + type: 'config-schema', + schema: anomalyDetectionJobsHealthRuleParams, + }, + }, actionVariables: { context: [ { diff --git a/x-pack/plugins/observability/common/constants.ts b/x-pack/plugins/observability/common/constants.ts index b10eafd3e608f..97d3d1d9eb938 100644 --- a/x-pack/plugins/observability/common/constants.ts +++ b/x-pack/plugins/observability/common/constants.ts @@ -62,3 +62,5 @@ export const observabilityRuleCreationValidConsumers: RuleCreationValidConsumer[ AlertConsumers.LOGS, AlertConsumers.OBSERVABILITY, ]; + +export const EventsAsUnit = 'events'; diff --git a/x-pack/plugins/observability/common/custom_threshold_rule/metric_value_formatter.ts b/x-pack/plugins/observability/common/custom_threshold_rule/metric_value_formatter.ts index 114f30fd85307..6d013fb038497 100644 --- a/x-pack/plugins/observability/common/custom_threshold_rule/metric_value_formatter.ts +++ b/x-pack/plugins/observability/common/custom_threshold_rule/metric_value_formatter.ts @@ -16,9 +16,9 @@ export const metricValueFormatter = (value: number | null, metric: string = '') } ); - const formatter = metric.endsWith('.pct') - ? createFormatter('percent') - : createFormatter('highPrecision'); + let formatter = createFormatter('highPrecision'); + if (metric.endsWith('.pct')) formatter = createFormatter('percent'); + if (metric.endsWith('.bytes')) formatter = createFormatter('bytes'); return value == null ? noDataValue : formatter(value); }; diff --git a/x-pack/plugins/observability/common/custom_threshold_rule/types.ts b/x-pack/plugins/observability/common/custom_threshold_rule/types.ts index 67849df1b59d7..d9943d539d21f 100644 --- a/x-pack/plugins/observability/common/custom_threshold_rule/types.ts +++ b/x-pack/plugins/observability/common/custom_threshold_rule/types.ts @@ -35,6 +35,9 @@ export enum Aggregators { MIN = 'min', MAX = 'max', CARDINALITY = 'cardinality', + RATE = 'rate', + P95 = 'p95', + P99 = 'p99', } export const aggType = fromEnum('Aggregators', Aggregators); export type AggType = rt.TypeOf; diff --git a/x-pack/plugins/observability/common/index.ts b/x-pack/plugins/observability/common/index.ts index 106eea543760f..f9467b8945060 100644 --- a/x-pack/plugins/observability/common/index.ts +++ b/x-pack/plugins/observability/common/index.ts @@ -33,6 +33,7 @@ export { apmLabsButton, enableInfrastructureHostsView, enableInfrastructureProfilingIntegration, + enableInfrastructureHostsCustomDashboards, enableAwsLambdaMetrics, enableAgentExplorerView, apmEnableTableSearchBar, diff --git a/x-pack/plugins/observability/common/slo/constants.ts b/x-pack/plugins/observability/common/slo/constants.ts index d49b1a805e192..7ef3ebb292e4d 100644 --- a/x-pack/plugins/observability/common/slo/constants.ts +++ b/x-pack/plugins/observability/common/slo/constants.ts @@ -33,6 +33,8 @@ export const getSLOTransformId = (sloId: string, sloRevision: number) => `slo-${sloId}-${sloRevision}`; export const DEFAULT_SLO_PAGE_SIZE = 25; +export const DEFAULT_SLO_GROUPS_PAGE_SIZE = 25; + export const getSLOSummaryTransformId = (sloId: string, sloRevision: number) => `slo-summary-${sloId}-${sloRevision}`; diff --git a/x-pack/plugins/observability/common/ui_settings_keys.ts b/x-pack/plugins/observability/common/ui_settings_keys.ts index 028103f56a207..2d2a76c6e36b0 100644 --- a/x-pack/plugins/observability/common/ui_settings_keys.ts +++ b/x-pack/plugins/observability/common/ui_settings_keys.ts @@ -19,6 +19,8 @@ export const apmLabsButton = 'observability:apmLabsButton'; export const enableInfrastructureHostsView = 'observability:enableInfrastructureHostsView'; export const enableInfrastructureProfilingIntegration = 'observability:enableInfrastructureProfilingIntegration'; +export const enableInfrastructureHostsCustomDashboards = + 'observability:enableInfrastructureHostsCustomDashboards'; export const enableAwsLambdaMetrics = 'observability:enableAwsLambdaMetrics'; export const enableAgentExplorerView = 'observability:apmAgentExplorerView'; export const apmEnableTableSearchBar = 'observability:apmEnableTableSearchBar'; diff --git a/x-pack/plugins/observability/public/components/burn_rate_rule_editor/burn_rate_rule_editor.tsx b/x-pack/plugins/observability/public/components/burn_rate_rule_editor/burn_rate_rule_editor.tsx index 4579640c6a5ed..70451de2cc9e6 100644 --- a/x-pack/plugins/observability/public/components/burn_rate_rule_editor/burn_rate_rule_editor.tsx +++ b/x-pack/plugins/observability/public/components/burn_rate_rule_editor/burn_rate_rule_editor.tsx @@ -16,12 +16,6 @@ import { BurnRateRuleParams, WindowSchema } from '../../typings'; import { SloSelector } from './slo_selector'; import { ValidationBurnRateRuleResult } from './validation'; import { createNewWindow, Windows } from './windows'; -import { - ALERT_ACTION, - HIGH_PRIORITY_ACTION, - LOW_PRIORITY_ACTION, - MEDIUM_PRIORITY_ACTION, -} from '../../../common/constants'; import { BURN_RATE_DEFAULTS } from './constants'; import { AlertTimeTable } from './alert_time_table'; @@ -38,54 +32,25 @@ export function BurnRateRuleEditor(props: Props) { }); const [selectedSlo, setSelectedSlo] = useState(undefined); + const [windowDefs, setWindowDefs] = useState(ruleParams?.windows || []); useEffect(() => { setSelectedSlo(initialSlo); + setWindowDefs((previous) => { + if (previous.length > 0) { + return previous; + } + return createDefaultWindows(initialSlo); + }); }, [initialSlo]); const onSelectedSlo = (slo: SLOResponse | undefined) => { setSelectedSlo(slo); - setRuleParams('sloId', slo?.id); - }; - - const [windowDefs, setWindowDefs] = useState( - ruleParams?.windows || [ - createNewWindow(selectedSlo, { - burnRateThreshold: 14.4, - longWindow: { value: 1, unit: 'h' }, - shortWindow: { value: 5, unit: 'm' }, - actionGroup: ALERT_ACTION.id, - }), - createNewWindow(selectedSlo, { - burnRateThreshold: 6, - longWindow: { value: 6, unit: 'h' }, - shortWindow: { value: 30, unit: 'm' }, - actionGroup: HIGH_PRIORITY_ACTION.id, - }), - createNewWindow(selectedSlo, { - burnRateThreshold: 3, - longWindow: { value: 24, unit: 'h' }, - shortWindow: { value: 120, unit: 'm' }, - actionGroup: MEDIUM_PRIORITY_ACTION.id, - }), - createNewWindow(selectedSlo, { - burnRateThreshold: 1, - longWindow: { value: 72, unit: 'h' }, - shortWindow: { value: 360, unit: 'm' }, - actionGroup: LOW_PRIORITY_ACTION.id, - }), - ] - ); - - // When the SLO changes, recalculate the max burn rates - useEffect(() => { setWindowDefs(() => { - const burnRateDefaults = selectedSlo - ? BURN_RATE_DEFAULTS[selectedSlo?.timeWindow.duration] - : BURN_RATE_DEFAULTS['30d']; - return burnRateDefaults.map((partialWindow) => createNewWindow(selectedSlo, partialWindow)); + return createDefaultWindows(slo); }); - }, [selectedSlo]); + setRuleParams('sloId', slo?.id); + }; useEffect(() => { setRuleParams('windows', windowDefs); @@ -131,3 +96,8 @@ export function BurnRateRuleEditor(props: Props) { ); } + +function createDefaultWindows(slo: SLOResponse | undefined) { + const burnRateDefaults = slo ? BURN_RATE_DEFAULTS[slo.timeWindow.duration] : []; + return burnRateDefaults.map((partialWindow) => createNewWindow(slo, partialWindow)); +} diff --git a/x-pack/plugins/observability/public/components/burn_rate_rule_editor/windows.tsx b/x-pack/plugins/observability/public/components/burn_rate_rule_editor/windows.tsx index 125361940c1fe..69e05a4537d75 100644 --- a/x-pack/plugins/observability/public/components/burn_rate_rule_editor/windows.tsx +++ b/x-pack/plugins/observability/public/components/burn_rate_rule_editor/windows.tsx @@ -17,7 +17,7 @@ import { EuiTitle, EuiSwitch, } from '@elastic/eui'; -import { SLOResponse } from '@kbn/slo-schema'; +import { CreateSLOInput, SLOResponse } from '@kbn/slo-schema'; import { i18n } from '@kbn/i18n'; import numeral from '@elastic/numeral'; import { v4 } from 'uuid'; @@ -51,7 +51,10 @@ const ACTION_GROUP_OPTIONS = [ { value: LOW_PRIORITY_ACTION.id, text: LOW_PRIORITY_ACTION.name }, ]; -export const calculateMaxBurnRateThreshold = (longWindow: Duration, slo?: SLOResponse) => { +export const calculateMaxBurnRateThreshold = ( + longWindow: Duration, + slo?: SLOResponse | CreateSLOInput +) => { return slo ? Math.floor(toMinutes(toDuration(slo.timeWindow.duration)) / toMinutes(longWindow)) : Infinity; @@ -244,7 +247,7 @@ const getErrorBudgetExhaustionText = ( }); export const createNewWindow = ( - slo?: SLOResponse, + slo?: SLOResponse | CreateSLOInput, partialWindow: Partial = {} ): WindowSchema => { const longWindow = partialWindow.longWindow || { value: 1, unit: 'h' }; diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/alert_details_app_section.test.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/alert_details_app_section.test.tsx index 80d7feb20a4e6..ab100bf98bd48 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/alert_details_app_section.test.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/alert_details_app_section.test.tsx @@ -86,7 +86,7 @@ describe('AlertDetailsAppSection', () => { it('should render rule and alert data', async () => { const result = renderComponent(); - expect((await result.findByTestId('thresholdAlertOverviewSection')).children.length).toBe(3); + expect((await result.findByTestId('thresholdAlertOverviewSection')).children.length).toBe(6); expect(result.getByTestId('thresholdRule-2000-2500')).toBeTruthy(); }); @@ -148,7 +148,30 @@ describe('AlertDetailsAppSection', () => { { ['kibana.alert.end']: '2023-03-28T14:40:00.000Z' } ); - expect(alertDetailsAppSectionComponent.getAllByTestId('RuleConditionChart').length).toBe(3); + expect(alertDetailsAppSectionComponent.getAllByTestId('RuleConditionChart').length).toBe(6); expect(mockedRuleConditionChart.mock.calls[0]).toMatchSnapshot(); }); + + it('should render title on condition charts', async () => { + const result = renderComponent(); + + expect(result.getByTestId('chartTitle-0').textContent).toBe( + 'Equation result for count (all documents)' + ); + expect(result.getByTestId('chartTitle-1').textContent).toBe( + 'Equation result for max (system.cpu.user.pct)' + ); + expect(result.getByTestId('chartTitle-2').textContent).toBe( + 'Equation result for min (system.memory.used.pct)' + ); + expect(result.getByTestId('chartTitle-3').textContent).toBe( + 'Equation result for min (system.memory.used.pct) + min (system.memory.used.pct) + min (system.memory.used.pct) + min (system.memory.used.pct...' + ); + expect(result.getByTestId('chartTitle-4').textContent).toBe( + 'Equation result for min (system.memory.used.pct) + min (system.memory.used.pct)' + ); + expect(result.getByTestId('chartTitle-5').textContent).toBe( + 'Equation result for min (system.memory.used.pct) + min (system.memory.used.pct) + min (system.memory.used.pct)' + ); + }); }); diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/alert_details_app_section.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/alert_details_app_section.tsx index 9d2aebddb43ee..2506516efd81a 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/alert_details_app_section.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/alert_details_app_section.tsx @@ -17,6 +17,9 @@ import { EuiSpacer, EuiText, EuiTitle, + EuiToolTip, + useEuiTheme, + transparentize, } from '@elastic/eui'; import { Rule, RuleTypeParams } from '@kbn/alerting-plugin/common'; import { getPaddedAlertTimeRange } from '@kbn/observability-get-padded-alert-time-range-util'; @@ -35,7 +38,6 @@ import type { RangeEventAnnotationConfig, } from '@kbn/event-annotation-common'; import moment from 'moment'; -import { transparentize, useEuiTheme } from '@elastic/eui'; import { useLicense } from '../../../../hooks/use_license'; import { useKibana } from '../../../../utils/kibana_react'; import { metricValueFormatter } from '../../../../../common/custom_threshold_rule/metric_value_formatter'; @@ -44,6 +46,7 @@ import { AlertParams, CustomThresholdAlertFields, CustomThresholdRuleTypeParams, + MetricExpression, } from '../../types'; import { TIME_LABELS } from '../criterion_preview_chart/criterion_preview_chart'; import { Threshold } from '../custom_threshold'; @@ -64,6 +67,44 @@ interface AppSectionProps { setAlertSummaryFields: React.Dispatch>; } +const CHART_TITLE_LIMIT = 120; + +const equationResultText = i18n.translate('xpack.observability.customThreshold.alertChartTitle', { + defaultMessage: 'Equation result for ', +}); + +const generateChartTitleAndTooltip = (criterion: MetricExpression) => { + const metricNameResolver: Record = {}; + + criterion.metrics.forEach( + (metric) => + (metricNameResolver[metric.name] = `${metric.aggType} (${ + metric.field ? metric.field : metric.filter ? metric.filter : 'all documents' + })`) + ); + + let equation = criterion.equation + ? criterion.equation + : criterion.metrics.map((m) => m.name).join(' + '); + + Object.keys(metricNameResolver) + .sort() + .reverse() + .forEach((metricName) => { + equation = equation.replaceAll(metricName, metricNameResolver[metricName]); + }); + + const chartTitle = + equation.length > CHART_TITLE_LIMIT + ? `${equation.substring(0, CHART_TITLE_LIMIT)}...` + : equation; + + return { + tooltip: `${equationResultText}${equation}`, + title: `${equationResultText}${chartTitle}`, + }; +}; + // eslint-disable-next-line import/no-default-export export default function AlertDetailsAppSection({ alert, @@ -89,6 +130,12 @@ export default function AlertDetailsAppSection({ const groups = alert.fields[ALERT_GROUP]; const tags = alert.fields[TAGS]; + const chartTitleAndTooltip: Array<{ title: string; tooltip: string }> = []; + + ruleParams.criteria.forEach((criterion) => { + chartTitleAndTooltip.push(generateChartTitleAndTooltip(criterion)); + }); + const alertStartAnnotation: PointInTimeEventAnnotationConfig = { label: 'Alert', type: 'manual', @@ -182,9 +229,11 @@ export default function AlertDetailsAppSection({ {ruleParams.criteria.map((criterion, index) => ( - -

{criterion.label || 'CUSTOM'}

-
+ + +

{chartTitleAndTooltip[index].title}

+
+
= (args) => const validationObject = validateCustomThreshold({ criteria: [expression as CustomMetricExpressionParams], searchConfiguration: {}, + uiSettings: {} as IUiSettingsClient, }); setErrors(validationObject.errors[0]); }, [expression]); diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/expression_row.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/expression_row.tsx index 6b0643791596a..9612b37e2d10f 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/components/expression_row.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/expression_row.tsx @@ -307,4 +307,31 @@ export const aggregationType: { [key: string]: AggregationType } = { value: Aggregators.SUM, validNormalizedTypes: ['number', 'histogram'], }, + p95: { + text: i18n.translate( + 'xpack.observability.customThreshold.rule.alertFlyout.aggregationText.p95', + { defaultMessage: '95th Percentile' } + ), + fieldRequired: false, + value: Aggregators.P95, + validNormalizedTypes: ['number', 'histogram'], + }, + p99: { + text: i18n.translate( + 'xpack.observability.customThreshold.rule.alertFlyout.aggregationText.p99', + { defaultMessage: '99th Percentile' } + ), + fieldRequired: false, + value: Aggregators.P99, + validNormalizedTypes: ['number', 'histogram'], + }, + rate: { + text: i18n.translate( + 'xpack.observability..customThreshold.rule.alertFlyout.aggregationText.rate', + { defaultMessage: 'Rate' } + ), + fieldRequired: false, + value: Aggregators.RATE, + validNormalizedTypes: ['number'], + }, }; diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/rule_condition_chart/helpers.test.ts b/x-pack/plugins/observability/public/components/custom_threshold/components/rule_condition_chart/helpers.test.ts new file mode 100644 index 0000000000000..4211907b5d4a0 --- /dev/null +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/rule_condition_chart/helpers.test.ts @@ -0,0 +1,138 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { + Aggregators, + CustomThresholdExpressionMetric, +} from '../../../../../common/custom_threshold_rule/types'; +import { getBufferThreshold, getLensOperationFromRuleMetric, lensFieldFormatter } from './helpers'; +const useCases = [ + [ + { + aggType: Aggregators.SUM, + field: 'system.cpu.user.pct', + filter: '', + name: '', + }, + 'sum(system.cpu.user.pct)', + ], + [ + { + aggType: Aggregators.MAX, + field: 'system.cpu.user.pct', + filter: '', + name: '', + }, + 'max(system.cpu.user.pct)', + ], + [ + { + aggType: Aggregators.MIN, + field: 'system.cpu.user.pct', + filter: '', + name: '', + }, + 'min(system.cpu.user.pct)', + ], + [ + { + aggType: Aggregators.AVERAGE, + field: 'system.cpu.user.pct', + filter: '', + name: '', + }, + 'average(system.cpu.user.pct)', + ], + [ + { + aggType: Aggregators.COUNT, + field: 'system.cpu.user.pct', + filter: '', + name: '', + }, + 'count(___records___)', + ], + [ + { + aggType: Aggregators.CARDINALITY, + field: 'system.cpu.user.pct', + filter: '', + name: '', + }, + 'unique_count(system.cpu.user.pct)', + ], + [ + { + aggType: Aggregators.P95, + field: 'system.cpu.user.pct', + filter: '', + name: '', + }, + 'percentile(system.cpu.user.pct, percentile=95)', + ], + [ + { + aggType: Aggregators.P99, + field: 'system.cpu.user.pct', + filter: '', + name: '', + }, + 'percentile(system.cpu.user.pct, percentile=99)', + ], + [ + { + aggType: Aggregators.RATE, + field: 'system.network.in.bytes', + filter: '', + name: '', + }, + `counter_rate(max(system.network.in.bytes), kql='')`, + ], + [ + { + aggType: Aggregators.RATE, + field: 'system.network.in.bytes', + filter: 'host.name : "foo"', + name: '', + }, + `counter_rate(max(system.network.in.bytes), kql='host.name : foo')`, + ], +]; + +test.each(useCases)('returns the correct operation from %p. => %p', (metric, expectedValue) => { + return expect(getLensOperationFromRuleMetric(metric as CustomThresholdExpressionMetric)).toEqual( + expectedValue + ); +}); + +describe('getBufferThreshold', () => { + const testData = [ + { threshold: undefined, buffer: '0.00' }, + { threshold: 0.1, buffer: '0.12' }, + { threshold: 0.01, buffer: '0.02' }, + { threshold: 0.001, buffer: '0.01' }, + { threshold: 0.00098, buffer: '0.01' }, + { threshold: 130, buffer: '143.00' }, + ]; + + it.each(testData)('getBufferThreshold($threshold) = $buffer', ({ threshold, buffer }) => { + expect(getBufferThreshold(threshold)).toBe(buffer); + }); +}); + +describe('lensFieldFormatter', () => { + const testData = [ + { metrics: [{ field: 'system.bytes' }], format: 'bits' }, + { metrics: [{ field: 'system.pct' }], format: 'percent' }, + { metrics: [{ field: 'system.host.cores' }], format: 'number' }, + { metrics: [{ field: undefined }], format: 'number' }, + ]; + it.each(testData)('getBufferThreshold($threshold) = $buffer', ({ metrics, format }) => { + expect(lensFieldFormatter(metrics as unknown as CustomThresholdExpressionMetric[])).toBe( + format + ); + }); +}); diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/rule_condition_chart/helpers.ts b/x-pack/plugins/observability/public/components/custom_threshold/components/rule_condition_chart/helpers.ts new file mode 100644 index 0000000000000..1875af6ceb93e --- /dev/null +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/rule_condition_chart/helpers.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 { + Aggregators, + CustomThresholdExpressionMetric, +} from '../../../../../common/custom_threshold_rule/types'; + +export const getLensOperationFromRuleMetric = (metric: CustomThresholdExpressionMetric): string => { + const { aggType, field, filter } = metric; + let operation: string = aggType; + const operationArgs: string[] = []; + const aggFilter = JSON.stringify(filter || '').replace(/"|\\/g, ''); + + if (aggType === Aggregators.RATE) { + return `counter_rate(max(${field}), kql='${aggFilter}')`; + } + + if (aggType === Aggregators.AVERAGE) operation = 'average'; + if (aggType === Aggregators.CARDINALITY) operation = 'unique_count'; + if (aggType === Aggregators.P95 || aggType === Aggregators.P99) operation = 'percentile'; + if (aggType === Aggregators.COUNT) operation = 'count'; + + let sourceField = field; + + if (aggType === Aggregators.COUNT) { + sourceField = '___records___'; + } + + operationArgs.push(sourceField || ''); + + if (aggType === Aggregators.P95) { + operationArgs.push('percentile=95'); + } + + if (aggType === Aggregators.P99) { + operationArgs.push('percentile=99'); + } + + if (aggFilter) operationArgs.push(`kql='${aggFilter}'`); + + return operation + '(' + operationArgs.join(', ') + ')'; +}; + +export const getBufferThreshold = (threshold?: number): string => + (Math.ceil((threshold || 0) * 1.1 * 100) / 100).toFixed(2).toString(); + +export const LensFieldFormat = { + NUMBER: 'number', + PERCENT: 'percent', + BITS: 'bits', +} as const; + +export const lensFieldFormatter = ( + metrics: CustomThresholdExpressionMetric[] +): typeof LensFieldFormat[keyof typeof LensFieldFormat] => { + if (metrics.length < 1 || !metrics[0].field) return LensFieldFormat.NUMBER; + const firstMetricField = metrics[0].field; + if (firstMetricField.endsWith('.pct')) return LensFieldFormat.PERCENT; + if (firstMetricField.endsWith('.bytes')) return LensFieldFormat.BITS; + return LensFieldFormat.NUMBER; +}; + +export const isRate = (metrics: CustomThresholdExpressionMetric[]): boolean => + Boolean(metrics.length > 0 && metrics[0].aggType === Aggregators.RATE); diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/rule_condition_chart/rule_condition_chart.test.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/rule_condition_chart/rule_condition_chart.test.tsx index 320958a20c0e8..7aab6dd0d636b 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/components/rule_condition_chart/rule_condition_chart.test.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/rule_condition_chart/rule_condition_chart.test.tsx @@ -13,7 +13,7 @@ import { Comparator, Aggregators } from '../../../../../common/custom_threshold_ import { useKibana } from '../../../../utils/kibana_react'; import { kibanaStartMock } from '../../../../utils/kibana_react.mock'; import { MetricExpression } from '../../types'; -import { getBufferThreshold, RuleConditionChart } from './rule_condition_chart'; +import { RuleConditionChart } from './rule_condition_chart'; jest.mock('../../../../utils/kibana_react'); @@ -71,18 +71,3 @@ describe('Rule condition chart', () => { expect(wrapper.find('[data-test-subj="thresholdRuleNoChartData"]').exists()).toBeTruthy(); }); }); - -describe('getBufferThreshold', () => { - const testData = [ - { threshold: undefined, buffer: '0.00' }, - { threshold: 0.1, buffer: '0.12' }, - { threshold: 0.01, buffer: '0.02' }, - { threshold: 0.001, buffer: '0.01' }, - { threshold: 0.00098, buffer: '0.01' }, - { threshold: 130, buffer: '143.00' }, - ]; - - it.each(testData)('getBufferThreshold($threshold) = $buffer', ({ threshold, buffer }) => { - expect(getBufferThreshold(threshold)).toBe(buffer); - }); -}); diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/rule_condition_chart/rule_condition_chart.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/rule_condition_chart/rule_condition_chart.tsx index bc2701eb79489..e1eefcfc2f706 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/components/rule_condition_chart/rule_condition_chart.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/rule_condition_chart/rule_condition_chart.tsx @@ -6,7 +6,7 @@ */ import React, { useState, useEffect } from 'react'; import { EuiEmptyPrompt, useEuiTheme } from '@elastic/eui'; -import { FillStyle, OperationType, SeriesType } from '@kbn/lens-plugin/public'; +import { FillStyle, SeriesType } from '@kbn/lens-plugin/public'; import { DataView } from '@kbn/data-views-plugin/common'; import { FormattedMessage } from '@kbn/i18n-react'; import useAsync from 'react-use/lib/useAsync'; @@ -19,19 +19,22 @@ import { XYReferenceLinesLayer, XYByValueAnnotationsLayer, } from '@kbn/lens-embeddable-utils'; - import { IErrorObject } from '@kbn/triggers-actions-ui-plugin/public'; import { i18n } from '@kbn/i18n'; import { TimeRange } from '@kbn/es-query'; import { EventAnnotationConfig } from '@kbn/event-annotation-common'; -import { - Aggregators, - Comparator, - AggType, -} from '../../../../../common/custom_threshold_rule/types'; +import { EventsAsUnit } from '../../../../../common/constants'; +import { Comparator } from '../../../../../common/custom_threshold_rule/types'; import { useKibana } from '../../../../utils/kibana_react'; import { MetricExpression } from '../../types'; import { AggMap, PainlessTinyMathParser } from './painless_tinymath_parser'; +import { + lensFieldFormatter, + getBufferThreshold, + getLensOperationFromRuleMetric, + isRate, + LensFieldFormat, +} from './helpers'; interface RuleConditionChartProps { metricExpression: MetricExpression; @@ -44,15 +47,6 @@ interface RuleConditionChartProps { seriesType?: SeriesType; } -const getOperationTypeFromRuleAggType = (aggType: AggType): OperationType => { - if (aggType === Aggregators.AVERAGE) return 'average'; - if (aggType === Aggregators.CARDINALITY) return 'unique_count'; - return aggType; -}; - -export const getBufferThreshold = (threshold?: number): string => - (Math.ceil((threshold || 0) * 1.1 * 100) / 100).toFixed(2).toString(); - export function RuleConditionChart({ metricExpression, dataView, @@ -107,13 +101,6 @@ export function RuleConditionChart({ useEffect(() => { if (!threshold) return; const refLayers = []; - const isPercent = Boolean(metrics.length === 1 && metrics[0].field?.endsWith('.pct')); - const format = { - id: isPercent ? 'percent' : 'number', - params: { - decimals: isPercent ? 0 : 2, - }, - }; if ( comparator === Comparator.OUTSIDE_RANGE || @@ -125,7 +112,6 @@ export function RuleConditionChart({ value: (threshold[0] || 0).toString(), color: euiTheme.colors.danger, fill: comparator === Comparator.OUTSIDE_RANGE ? 'below' : 'none', - format, }, ], }); @@ -135,7 +121,6 @@ export function RuleConditionChart({ value: (threshold[1] || 0).toString(), color: euiTheme.colors.danger, fill: comparator === Comparator.OUTSIDE_RANGE ? 'above' : 'none', - format, }, ], }); @@ -152,7 +137,6 @@ export function RuleConditionChart({ value: (threshold[0] || 0).toString(), color: euiTheme.colors.danger, fill, - format, }, ], }); @@ -163,7 +147,6 @@ export function RuleConditionChart({ value: getBufferThreshold(threshold[0]), color: 'transparent', fill, - format, }, ], }); @@ -191,17 +174,7 @@ export function RuleConditionChart({ return; } const aggMapFromMetrics = metrics.reduce((acc, metric) => { - const operation = getOperationTypeFromRuleAggType(metric.aggType); - let sourceField = metric.field; - - if (metric.aggType === Aggregators.COUNT) { - sourceField = '___records___'; - } - let operationField = `${operation}(${sourceField})`; - if (metric?.filter) { - const aggFilter = JSON.stringify(metric.filter).replace(/"|\\/g, ''); - operationField = `${operation}(${sourceField},kql='${aggFilter}')`; - } + const operationField = getLensOperationFromRuleMetric(metric); return { ...acc, [metric.name]: operationField, @@ -232,16 +205,17 @@ export function RuleConditionChart({ if (!formulaAsync.value || !dataView || !formula) { return; } - const isPercent = Boolean(metrics.length === 1 && metrics[0].field?.endsWith('.pct')); + const formatId = lensFieldFormatter(metrics); const baseLayer = { type: 'formula', value: formula, label: 'Custom Threshold', groupBy, format: { - id: isPercent ? 'percent' : 'number', + id: formatId, params: { - decimals: isPercent ? 0 : 2, + decimals: formatId === LensFieldFormat.PERCENT ? 0 : 2, + suffix: isRate(metrics) && formatId === LensFieldFormat.NUMBER ? EventsAsUnit : undefined, }, }, }; @@ -273,6 +247,8 @@ export function RuleConditionChart({ value: layer.value, label: layer.label, format: layer.format, + // We always scale the chart with seconds with RATE Agg. + timeScale: isRate(metrics) ? 's' : undefined, })), options: xYDataLayerOptions, }); diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/validation.test.ts b/x-pack/plugins/observability/public/components/custom_threshold/components/validation.test.ts index ac1b545e4a302..f1c5bc9576763 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/components/validation.test.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/validation.test.ts @@ -5,7 +5,25 @@ * 2.0. */ -import { EQUATION_REGEX } from './validation'; +import { IUiSettingsClient } from '@kbn/core-ui-settings-browser'; +import { + CustomMetricExpressionParams, + CustomThresholdExpressionMetric, +} from '../../../../common/custom_threshold_rule/types'; +import { EQUATION_REGEX, validateCustomThreshold } from './validation'; + +const errorReason = 'this should appear as error reason'; + +jest.mock('@kbn/es-query', () => { + const actual = jest.requireActual('@kbn/es-query'); + return { + ...actual, + buildEsQuery: jest.fn(() => { + // eslint-disable-next-line no-throw-literal + throw { shortMessage: errorReason }; + }), + }; +}); describe('Metric Threshold Validation', () => { describe('valid equations', () => { @@ -30,4 +48,46 @@ describe('Metric Threshold Validation', () => { }); }); }); + it('should throw an error when data view is not provided', () => { + const res = validateCustomThreshold({ + uiSettings: {} as IUiSettingsClient, + searchConfiguration: {}, + criteria: { + metrics: [ + { + name: 'Test', + aggType: 'count', + field: 'system.cpu.cores', + filter: 'none valid filter', + }, + ] as unknown as CustomThresholdExpressionMetric[], + } as unknown as CustomMetricExpressionParams[], + }); + expect(res.errors.searchConfiguration[0]).toBe('Data view is required.'); + }); + it('should throw an error when filter query is not valid with reason', () => { + const res = validateCustomThreshold({ + uiSettings: { + get: jest.fn(), + } as unknown as IUiSettingsClient, + searchConfiguration: { + index: 'test*', + query: { + language: `kuery`, + query: 'test:tet', + }, + }, + criteria: { + metrics: [ + { + name: 'Test', + aggType: 'count', + field: 'system.cpu.cores', + filter: 'none valid filter', + }, + ] as unknown as CustomThresholdExpressionMetric[], + } as unknown as CustomMetricExpressionParams[], + }); + expect(res.errors.filterQuery[0]).toBe(`Filter query is invalid. ${errorReason}`); + }); }); diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/validation.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/validation.tsx index 2144757216fbe..eb7e86c090dc2 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/components/validation.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/validation.tsx @@ -5,7 +5,8 @@ * 2.0. */ -import { Query, SerializedSearchSourceFields } from '@kbn/data-plugin/common'; +import { IUiSettingsClient } from '@kbn/core-ui-settings-browser'; +import { getEsQueryConfig, Query, SerializedSearchSourceFields } from '@kbn/data-plugin/common'; import { buildEsQuery, fromKueryExpression } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; import { ValidationResult } from '@kbn/triggers-actions-ui-plugin/public'; @@ -20,9 +21,11 @@ export const EQUATION_REGEX = /[^A-Z|+|\-|\s|\d+|\.|\(|\)|\/|\*|>|<|=|\?|\:|&|\! export function validateCustomThreshold({ criteria, searchConfiguration, + uiSettings, }: { criteria: CustomMetricExpressionParams[]; searchConfiguration: SerializedSearchSourceFields; + uiSettings: IUiSettingsClient; }): ValidationResult { const validationResult = { errors: {} }; const errors: { @@ -56,14 +59,17 @@ export function validateCustomThreshold({ buildEsQuery( undefined, [{ query: (searchConfiguration.query as Query).query, language: 'kuery' }], - [] + [], + getEsQueryConfig(uiSettings) ); } catch (e) { + const errorReason = e.shortMessage || ''; errors.filterQuery = [ i18n.translate( 'xpack.observability.customThreshold.rule.alertFlyout.error.invalidFilterQuery', { - defaultMessage: 'Filter query is invalid.', + values: { errorReason }, + defaultMessage: `Filter query is invalid. {errorReason}`, } ), ]; diff --git a/x-pack/plugins/observability/public/components/custom_threshold/mocks/custom_threshold_rule.ts b/x-pack/plugins/observability/public/components/custom_threshold/mocks/custom_threshold_rule.ts index 124003879e53e..7616b1b9e2953 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/mocks/custom_threshold_rule.ts +++ b/x-pack/plugins/observability/public/components/custom_threshold/mocks/custom_threshold_rule.ts @@ -103,6 +103,62 @@ export const buildCustomThresholdRule = ( timeSize: 15, timeUnit: 'm', }, + { + comparator: Comparator.GT, + metrics: [ + { + name: 'A', + aggType: Aggregators.MIN, + field: 'system.memory.used.pct', + }, + ], + threshold: [0.8], + timeSize: 15, + timeUnit: 'm', + equation: + 'A + A + A + A + A + A + A + A + A + A + A + A + A + A + A + A + A + A + A + A + A', + }, + { + comparator: Comparator.GT, + metrics: [ + { + name: 'C', + aggType: Aggregators.MIN, + field: 'system.memory.used.pct', + }, + { + name: 'D', + aggType: Aggregators.MIN, + field: 'system.memory.used.pct', + }, + ], + threshold: [0.8], + timeSize: 15, + timeUnit: 'm', + }, + { + comparator: Comparator.GT, + metrics: [ + { + name: 'CAD', + aggType: Aggregators.MIN, + field: 'system.memory.used.pct', + }, + { + name: 'CADE', + aggType: Aggregators.MIN, + field: 'system.memory.used.pct', + }, + { + name: 'ADE', + aggType: Aggregators.MIN, + field: 'system.memory.used.pct', + }, + ], + threshold: [0.8], + timeSize: 15, + timeUnit: 'm', + }, ], searchConfiguration: { query: { diff --git a/x-pack/plugins/observability/public/hooks/slo/query_key_factory.ts b/x-pack/plugins/observability/public/hooks/slo/query_key_factory.ts index c0be8dfd5f6e2..a08edd6d49f8e 100644 --- a/x-pack/plugins/observability/public/hooks/slo/query_key_factory.ts +++ b/x-pack/plugins/observability/public/hooks/slo/query_key_factory.ts @@ -17,10 +17,21 @@ interface SloListFilter { lastRefresh?: number; } +interface SloGroupListFilter { + page: number; + perPage: number; + groupBy: string; + kqlQuery: string; + filters: string; + lastRefresh?: number; +} + export const sloKeys = { all: ['slo'] as const, lists: () => [...sloKeys.all, 'list'] as const, list: (filters: SloListFilter) => [...sloKeys.lists(), filters] as const, + group: (filters: SloGroupListFilter) => [...sloKeys.groups(), filters] as const, + groups: () => [...sloKeys.all, 'group'] as const, details: () => [...sloKeys.all, 'details'] as const, detail: (sloId?: string) => [...sloKeys.details(), sloId] as const, rules: () => [...sloKeys.all, 'rules'] as const, diff --git a/x-pack/plugins/observability/public/hooks/slo/use_fetch_historical_summary.ts b/x-pack/plugins/observability/public/hooks/slo/use_fetch_historical_summary.ts index 63bdc55d7eaf8..afa1c6e597723 100644 --- a/x-pack/plugins/observability/public/hooks/slo/use_fetch_historical_summary.ts +++ b/x-pack/plugins/observability/public/hooks/slo/use_fetch_historical_summary.ts @@ -49,6 +49,7 @@ export function useFetchHistoricalSummary({ // ignore error } }, + enabled: Boolean(list.length > 0), refetchInterval: shouldRefetch ? SLO_LONG_REFETCH_INTERVAL : undefined, refetchOnWindowFocus: false, keepPreviousData: true, diff --git a/x-pack/plugins/observability/public/hooks/slo/use_fetch_slo_groups.ts b/x-pack/plugins/observability/public/hooks/slo/use_fetch_slo_groups.ts new file mode 100644 index 0000000000000..9ca0ffa0cbfb7 --- /dev/null +++ b/x-pack/plugins/observability/public/hooks/slo/use_fetch_slo_groups.ts @@ -0,0 +1,119 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { useQuery } from '@tanstack/react-query'; +import { i18n } from '@kbn/i18n'; +import { buildQueryFromFilters, Filter } from '@kbn/es-query'; +import { useMemo } from 'react'; +import { FindSLOGroupsResponse } from '@kbn/slo-schema'; +import { useKibana } from '../../utils/kibana_react'; +import { useCreateDataView } from '../use_create_data_view'; +import { sloKeys } from './query_key_factory'; +import { DEFAULT_SLO_GROUPS_PAGE_SIZE } from '../../../common/slo/constants'; +import { SearchState } from '../../pages/slos/hooks/use_url_search_state'; +import { SLO_SUMMARY_DESTINATION_INDEX_NAME } from '../../../common/slo/constants'; + +interface SLOGroupsParams { + page?: number; + perPage?: number; + groupBy?: string; + kqlQuery?: string; + tagsFilter?: SearchState['tagsFilter']; + statusFilter?: SearchState['statusFilter']; + filters?: Filter[]; + lastRefresh?: number; +} + +interface UseFetchSloGroupsResponse { + isLoading: boolean; + isRefetching: boolean; + isSuccess: boolean; + isError: boolean; + data: FindSLOGroupsResponse | undefined; +} + +export function useFetchSloGroups({ + page = 1, + perPage = DEFAULT_SLO_GROUPS_PAGE_SIZE, + groupBy = 'ungrouped', + kqlQuery = '', + tagsFilter, + statusFilter, + filters: filterDSL = [], + lastRefresh, +}: SLOGroupsParams = {}): UseFetchSloGroupsResponse { + const { + http, + notifications: { toasts }, + } = useKibana().services; + + const { dataView } = useCreateDataView({ + indexPatternString: SLO_SUMMARY_DESTINATION_INDEX_NAME, + }); + + const filters = useMemo(() => { + try { + return JSON.stringify( + buildQueryFromFilters( + [ + ...filterDSL, + ...(tagsFilter ? [tagsFilter] : []), + ...(statusFilter ? [statusFilter] : []), + ], + dataView, + { + ignoreFilterIfFieldNotInIndex: true, + } + ) + ); + } catch (e) { + return ''; + } + }, [filterDSL, tagsFilter, statusFilter, dataView]); + + const { data, isLoading, isSuccess, isError, isRefetching } = useQuery({ + queryKey: sloKeys.group({ page, perPage, groupBy, kqlQuery, filters, lastRefresh }), + queryFn: async ({ signal }) => { + const response = await http.get( + '/internal/api/observability/slos/_groups', + { + query: { + ...(page && { page }), + ...(perPage && { perPage }), + ...(groupBy && { groupBy }), + ...(kqlQuery && { kqlQuery }), + ...(filters && { filters }), + }, + signal, + } + ); + return response; + }, + cacheTime: 0, + refetchOnWindowFocus: false, + retry: (failureCount, error) => { + if (String(error) === 'Error: Forbidden') { + return false; + } + return failureCount < 4; + }, + onError: (error: Error) => { + toasts.addError(error, { + title: i18n.translate('xpack.observability.slo.groups.list.errorNotification', { + defaultMessage: 'Something went wrong while fetching SLO Groups', + }), + }); + }, + }); + + return { + data, + isLoading, + isSuccess, + isError, + isRefetching, + }; +} diff --git a/x-pack/plugins/observability/public/hooks/slo/use_fetch_slo_list.ts b/x-pack/plugins/observability/public/hooks/slo/use_fetch_slo_list.ts index 945e57d5bfaee..762c49439f416 100644 --- a/x-pack/plugins/observability/public/hooks/slo/use_fetch_slo_list.ts +++ b/x-pack/plugins/observability/public/hooks/slo/use_fetch_slo_list.ts @@ -20,7 +20,7 @@ import { import { useKibana } from '../../utils/kibana_react'; import { sloKeys } from './query_key_factory'; -interface SLOListParams { +export interface SLOListParams { kqlQuery?: string; page?: number; sortBy?: string; @@ -30,6 +30,7 @@ interface SLOListParams { lastRefresh?: number; tagsFilter?: SearchState['tagsFilter']; statusFilter?: SearchState['statusFilter']; + disabled?: boolean; } export interface UseFetchSloListResponse { @@ -51,6 +52,7 @@ export function useFetchSloList({ lastRefresh, tagsFilter, statusFilter, + disabled = false, }: SLOListParams = {}): UseFetchSloListResponse { const { http, @@ -98,13 +100,14 @@ export function useFetchSloList({ ...(kqlQuery && { kqlQuery }), ...(sortBy && { sortBy }), ...(sortDirection && { sortDirection }), - ...(page && { page }), - ...(perPage && { perPage }), + ...(page !== undefined && { page }), + ...(perPage !== undefined && { perPage }), ...(filters && { filters }), }, signal, }); }, + enabled: !disabled, cacheTime: 0, refetchOnWindowFocus: false, retry: (failureCount, error) => { diff --git a/x-pack/plugins/observability/public/hooks/slo/use_get_preview_data.ts b/x-pack/plugins/observability/public/hooks/slo/use_get_preview_data.ts index 1cf3cb1c0a002..4f0963629bae1 100644 --- a/x-pack/plugins/observability/public/hooks/slo/use_get_preview_data.ts +++ b/x-pack/plugins/observability/public/hooks/slo/use_get_preview_data.ts @@ -22,8 +22,7 @@ export function useGetPreviewData( isValid: boolean, indicator: Indicator, range: { start: number; end: number }, - objective?: Objective, - filter?: string + objective?: Objective ): UseGetPreviewData { const { http } = useKibana().services; @@ -34,9 +33,9 @@ export function useGetPreviewData( '/internal/observability/slos/_preview', { body: JSON.stringify({ - indicator: { ...indicator, params: { ...indicator.params, filter } }, + indicator, range, - ...((objective && { objective }) || {}), + ...(objective ? { objective } : null), }), signal, } diff --git a/x-pack/plugins/observability/public/hooks/use_create_rule.ts b/x-pack/plugins/observability/public/hooks/use_create_rule.ts new file mode 100644 index 0000000000000..7c30544105aaa --- /dev/null +++ b/x-pack/plugins/observability/public/hooks/use_create_rule.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 { useMutation } from '@tanstack/react-query'; +import { i18n } from '@kbn/i18n'; +import { BASE_ALERTING_API_PATH, RuleTypeParams } from '@kbn/alerting-plugin/common'; +import { v4 } from 'uuid'; +import { + CreateRuleRequestBody, + CreateRuleResponse, +} from '@kbn/alerting-plugin/common/routes/rule/apis/create'; +import { useKibana } from '../utils/kibana_react'; + +export function useCreateRule() { + const { + http, + notifications: { toasts }, + } = useKibana().services; + + const createRule = useMutation< + CreateRuleResponse, + Error, + { rule: CreateRuleRequestBody } + >( + ['createRule'], + ({ rule }) => { + try { + const ruleId = v4(); + const body = JSON.stringify(rule); + return http.post(`${BASE_ALERTING_API_PATH}/rule/${ruleId}`, { + body, + }); + } catch (e) { + throw new Error(`Unable to create rule: ${e}`); + } + }, + { + onError: (_err) => { + toasts.addDanger( + i18n.translate('xpack.observability.rules.createRule.errorNotification.descriptionText', { + defaultMessage: 'Failed to create rule', + }) + ); + }, + + onSuccess: () => { + toasts.addSuccess( + i18n.translate( + 'xpack.observability.rules.createRule.successNotification.descriptionText', + { + defaultMessage: 'Rule created', + } + ) + ); + }, + } + ); + + return createRule; +} diff --git a/x-pack/plugins/observability/public/locators/slo_list.test.ts b/x-pack/plugins/observability/public/locators/slo_list.test.ts index bde41450377f1..f78746964334c 100644 --- a/x-pack/plugins/observability/public/locators/slo_list.test.ts +++ b/x-pack/plugins/observability/public/locators/slo_list.test.ts @@ -14,7 +14,7 @@ describe('SloListLocator', () => { const location = await locator.getLocation({}); expect(location.app).toEqual('observability'); expect(location.path).toMatchInlineSnapshot( - `"/slos?search=(filters:!(),kqlQuery:'',lastRefresh:0,page:0,perPage:25,sort:(by:status,direction:desc),view:cardView)"` + `"/slos?search=(filters:!(),groupBy:ungrouped,kqlQuery:'',lastRefresh:0,page:0,perPage:25,sort:(by:status,direction:desc),view:cardView)"` ); }); @@ -24,7 +24,7 @@ describe('SloListLocator', () => { }); expect(location.app).toEqual('observability'); expect(location.path).toMatchInlineSnapshot( - `"/slos?search=(filters:!(),kqlQuery:'slo.name:%20%22Service%20Availability%22%20and%20slo.indicator.type%20:%20%22sli.kql.custom%22',lastRefresh:0,page:0,perPage:25,sort:(by:status,direction:desc),view:cardView)"` + `"/slos?search=(filters:!(),groupBy:ungrouped,kqlQuery:'slo.name:%20%22Service%20Availability%22%20and%20slo.indicator.type%20:%20%22sli.kql.custom%22',lastRefresh:0,page:0,perPage:25,sort:(by:status,direction:desc),view:cardView)"` ); }); }); diff --git a/x-pack/plugins/observability/public/pages/slo_details/components/events_chart_panel.tsx b/x-pack/plugins/observability/public/pages/slo_details/components/events_chart_panel.tsx index 2cca59fad87af..7dcfb6edc9081 100644 --- a/x-pack/plugins/observability/public/pages/slo_details/components/events_chart_panel.tsx +++ b/x-pack/plugins/observability/public/pages/slo_details/components/events_chart_panel.tsx @@ -33,9 +33,9 @@ import numeral from '@elastic/numeral'; import { useActiveCursor } from '@kbn/charts-plugin/public'; import { i18n } from '@kbn/i18n'; import { ALL_VALUE, SLOWithSummaryResponse } from '@kbn/slo-schema'; +import { cloneDeep, max, min } from 'lodash'; import moment from 'moment'; import React, { useRef } from 'react'; -import { max, min } from 'lodash'; import { useGetPreviewData } from '../../../hooks/slo/use_get_preview_data'; import { useKibana } from '../../../utils/kibana_react'; import { COMPARATOR_MAPPING } from '../../slo_edit/constants'; @@ -51,14 +51,23 @@ export interface Props { export function EventsChartPanel({ slo, range }: Props) { const { charts, uiSettings } = useKibana().services; const { euiTheme } = useEuiTheme(); - const filter = slo.instanceId !== ALL_VALUE ? `${slo.groupBy}: "${slo.instanceId}"` : ''; - const { isLoading, data } = useGetPreviewData(true, slo.indicator, range, slo.objective, filter); const baseTheme = charts.theme.useChartsBaseTheme(); const chartRef = useRef(null); const handleCursorUpdate = useActiveCursor(charts.activeCursor, chartRef, { isDateHistogram: true, }); + const instanceIdFilter = + slo.instanceId !== ALL_VALUE ? `${slo.groupBy}: "${slo.instanceId}"` : null; + const sloIndicator = cloneDeep(slo.indicator); + if (instanceIdFilter) { + sloIndicator.params.filter = + !!sloIndicator.params.filter && sloIndicator.params.filter.length > 0 + ? `${sloIndicator.params.filter} and ${instanceIdFilter}` + : instanceIdFilter; + } + const { isLoading, data } = useGetPreviewData(true, sloIndicator, range, slo.objective); + const dateFormat = uiSettings.get('dateFormat'); const title = diff --git a/x-pack/plugins/observability/public/pages/slo_edit/components/slo_edit_form.tsx b/x-pack/plugins/observability/public/pages/slo_edit/components/slo_edit_form.tsx index bf32535118293..dfc5fb1a6f563 100644 --- a/x-pack/plugins/observability/public/pages/slo_edit/components/slo_edit_form.tsx +++ b/x-pack/plugins/observability/public/pages/slo_edit/components/slo_edit_form.tsx @@ -5,25 +5,15 @@ * 2.0. */ -import { - EuiButton, - EuiButtonEmpty, - EuiCheckbox, - EuiFlexGroup, - EuiIconTip, - EuiSpacer, - EuiSteps, -} from '@elastic/eui'; +import { EuiButton, EuiButtonEmpty, EuiFlexGroup, EuiSpacer, EuiSteps } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import type { GetSLOResponse } from '@kbn/slo-schema'; -import React, { useCallback, useEffect, useState } from 'react'; +import React, { useCallback } from 'react'; import { FormProvider, useForm } from 'react-hook-form'; import { InspectSLOPortal } from './common/inspect_slo_portal'; import { EquivalentApiRequest } from './common/equivalent_api_request'; -import { BurnRateRuleFlyout } from '../../slos/components/common/burn_rate_rule_flyout'; import { paths } from '../../../../common/locators/paths'; import { useCreateSlo } from '../../../hooks/slo/use_create_slo'; -import { useFetchRulesForSlo } from '../../../hooks/slo/use_fetch_rules_for_slo'; import { useUpdateSlo } from '../../../hooks/slo/use_update_slo'; import { useKibana } from '../../../utils/kibana_react'; import { SLO_EDIT_FORM_DEFAULT_VALUES } from '../constants'; @@ -32,10 +22,6 @@ import { transformSloResponseToCreateSloForm, transformValuesToUpdateSLOInput, } from '../helpers/process_slo_form_values'; -import { - CREATE_RULE_SEARCH_PARAM, - useAddRuleFlyoutState, -} from '../hooks/use_add_rule_flyout_state'; import { useParseUrlState } from '../hooks/use_parse_url_state'; import { useSectionFormValidation } from '../hooks/use_section_form_validation'; import { useShowSections } from '../hooks/use_show_sections'; @@ -43,6 +29,9 @@ import { CreateSLOForm } from '../types'; import { SloEditFormDescriptionSection } from './slo_edit_form_description_section'; import { SloEditFormIndicatorSection } from './slo_edit_form_indicator_section'; import { SloEditFormObjectiveSection } from './slo_edit_form_objective_section'; +import { useCreateRule } from '../../../hooks/use_create_rule'; +import { createBurnRateRuleRequestBody } from '../helpers/create_burn_rate_rule_request_body'; +import { BurnRateRuleParams } from '../../../typings'; export interface Props { slo?: GetSLOResponse; @@ -57,22 +46,9 @@ export function SloEditForm({ slo }: Props) { } = useKibana().services; const isEditMode = slo !== undefined; - const { data: rules, isInitialLoading } = useFetchRulesForSlo({ - sloIds: slo?.id ? [slo.id] : undefined, - }); - const sloFormValuesFromUrlState = useParseUrlState(); const sloFormValuesFromSloResponse = transformSloResponseToCreateSloForm(slo); - const isAddRuleFlyoutOpen = useAddRuleFlyoutState(isEditMode); - const [isCreateRuleCheckboxChecked, setIsCreateRuleCheckboxChecked] = useState(true); - - useEffect(() => { - if (isEditMode && rules && rules[slo.id].length) { - setIsCreateRuleCheckboxChecked(false); - } - }, [isEditMode, rules, slo]); - const methods = useForm({ defaultValues: SLO_EDIT_FORM_DEFAULT_VALUES, values: sloFormValuesFromUrlState ? sloFormValuesFromUrlState : sloFormValuesFromSloResponse, @@ -97,6 +73,8 @@ export function SloEditForm({ slo }: Props) { const { mutateAsync: createSlo, isLoading: isCreateSloLoading } = useCreateSlo(); const { mutateAsync: updateSlo, isLoading: isUpdateSloLoading } = useUpdateSlo(); + const { mutateAsync: createBurnRateRule, isLoading: isCreateBurnRateRuleLoading } = + useCreateRule(); const handleSubmit = async () => { const isValid = await trigger(); @@ -108,30 +86,15 @@ export function SloEditForm({ slo }: Props) { if (isEditMode) { const processedValues = transformValuesToUpdateSLOInput(values); - - if (isCreateRuleCheckboxChecked) { - await updateSlo({ sloId: slo.id, slo: processedValues }); - navigate( - basePath.prepend( - `${paths.observability.sloEdit(slo.id)}?${CREATE_RULE_SEARCH_PARAM}=true` - ) - ); - } else { - updateSlo({ sloId: slo.id, slo: processedValues }); - navigate(basePath.prepend(paths.observability.slos)); - } + updateSlo({ sloId: slo.id, slo: processedValues }); + navigate(basePath.prepend(paths.observability.slos)); } else { const processedValues = transformCreateSLOFormToCreateSLOInput(values); - - if (isCreateRuleCheckboxChecked) { - const { id } = await createSlo({ slo: processedValues }); - navigate( - basePath.prepend(`${paths.observability.sloEdit(id)}?${CREATE_RULE_SEARCH_PARAM}=true`) - ); - } else { - createSlo({ slo: processedValues }); - navigate(basePath.prepend(paths.observability.slos)); - } + const resp = await createSlo({ slo: processedValues }); + await createBurnRateRule({ + rule: createBurnRateRuleRequestBody({ ...processedValues, id: resp.id }), + }); + navigate(basePath.prepend(paths.observability.slos)); } }; @@ -140,10 +103,6 @@ export function SloEditForm({ slo }: Props) { [navigateToUrl] ); - const handleChangeCheckbox = () => { - setIsCreateRuleCheckboxChecked(!isCreateRuleCheckboxChecked); - }; - return ( <> @@ -175,36 +134,6 @@ export function SloEditForm({ slo }: Props) { ]} /> - - - - {i18n.translate('xpack.observability.slo.sloEdit.createAlert.title', { - defaultMessage: 'Create an', - })}{' '} - - {i18n.translate('xpack.observability.slo.sloEdit.createAlert.ruleName', { - defaultMessage: 'SLO burn rate alert rule', - })} - - - - - } - onChange={handleChangeCheckbox} - /> - - @@ -212,7 +141,7 @@ export function SloEditForm({ slo }: Props) { color="primary" data-test-subj="sloFormSubmitButton" fill - isLoading={isCreateSloLoading || isUpdateSloLoading} + isLoading={isCreateSloLoading || isUpdateSloLoading || isCreateBurnRateRuleLoading} onClick={handleSubmit} > {isEditMode @@ -243,12 +172,6 @@ export function SloEditForm({ slo }: Props) { - - ); } diff --git a/x-pack/plugins/observability/public/pages/slo_edit/components/timeslice_metric/metric_indicator.tsx b/x-pack/plugins/observability/public/pages/slo_edit/components/timeslice_metric/metric_indicator.tsx index b8105814b852e..d3098208fa6c1 100644 --- a/x-pack/plugins/observability/public/pages/slo_edit/components/timeslice_metric/metric_indicator.tsx +++ b/x-pack/plugins/observability/public/pages/slo_edit/components/timeslice_metric/metric_indicator.tsx @@ -270,10 +270,11 @@ export function MetricIndicator({ indexFields, isLoadingIndex }: MetricIndicator defaultValue={0} render={({ field: { ref, ...field }, fieldState }) => ( field.onChange(Number(event.target.value))} diff --git a/x-pack/plugins/observability/public/pages/slo_edit/helpers/create_burn_rate_rule_request_body.ts b/x-pack/plugins/observability/public/pages/slo_edit/helpers/create_burn_rate_rule_request_body.ts new file mode 100644 index 0000000000000..0128d6c7d6360 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/slo_edit/helpers/create_burn_rate_rule_request_body.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 { CreateSLOInput } from '@kbn/slo-schema'; +import { i18n } from '@kbn/i18n'; +import { CreateRuleRequestBody } from '@kbn/alerting-plugin/common/routes/rule/apis/create'; +import { BURN_RATE_DEFAULTS } from '../../../components/burn_rate_rule_editor/constants'; +import { createNewWindow } from '../../../components/burn_rate_rule_editor/windows'; +import { BurnRateRuleParams } from '../../../typings'; + +function createBurnRateWindowsFromSLO(slo: CreateSLOInput) { + const burnRateDefaults = slo + ? BURN_RATE_DEFAULTS[slo?.timeWindow.duration] + : BURN_RATE_DEFAULTS['30d']; + return burnRateDefaults.map((partialWindow) => createNewWindow(slo, partialWindow)); +} + +export function createBurnRateRuleRequestBody( + slo: CreateSLOInput & { id: string } +): CreateRuleRequestBody { + return { + params: { + sloId: slo.id, + windows: createBurnRateWindowsFromSLO(slo), + }, + consumer: 'slo', + schedule: { interval: '1m' }, + tags: [], + name: i18n.translate('xpack.observability.slo.burnRateRule.name', { + defaultMessage: '{name} Burn Rate rule', + values: { name: slo.name }, + }), + rule_type_id: 'slo.rules.burnRate', + actions: [], + enabled: true, + }; +} diff --git a/x-pack/plugins/observability/public/pages/slo_edit/slo_edit.test.tsx b/x-pack/plugins/observability/public/pages/slo_edit/slo_edit.test.tsx index 9ef10ea1d928a..f843109f53542 100644 --- a/x-pack/plugins/observability/public/pages/slo_edit/slo_edit.test.tsx +++ b/x-pack/plugins/observability/public/pages/slo_edit/slo_edit.test.tsx @@ -20,6 +20,7 @@ import { useCreateSlo } from '../../hooks/slo/use_create_slo'; import { useFetchApmSuggestions } from '../../hooks/slo/use_fetch_apm_suggestions'; import { useFetchSloDetails } from '../../hooks/slo/use_fetch_slo_details'; import { useUpdateSlo } from '../../hooks/slo/use_update_slo'; +import { useCreateRule } from '../../hooks/use_create_rule'; import { useFetchDataViews } from '../../hooks/use_fetch_data_views'; import { useFetchIndices } from '../../hooks/use_fetch_indices'; import { useKibana } from '../../utils/kibana_react'; @@ -39,6 +40,7 @@ jest.mock('../../hooks/use_fetch_data_views'); jest.mock('../../hooks/slo/use_fetch_slo_details'); jest.mock('../../hooks/slo/use_create_slo'); jest.mock('../../hooks/slo/use_update_slo'); +jest.mock('../../hooks/use_create_rule'); jest.mock('../../hooks/slo/use_fetch_apm_suggestions'); jest.mock('../../hooks/slo/use_capabilities'); @@ -54,6 +56,7 @@ const useFetchDataViewsMock = useFetchDataViews as jest.Mock; const useFetchSloMock = useFetchSloDetails as jest.Mock; const useCreateSloMock = useCreateSlo as jest.Mock; const useUpdateSloMock = useUpdateSlo as jest.Mock; +const useCreateRuleMock = useCreateRule as jest.Mock; const useFetchApmSuggestionsMock = useFetchApmSuggestions as jest.Mock; const useCapabilitiesMock = useCapabilities as jest.Mock; @@ -131,8 +134,9 @@ const mockKibana = (license: ILicense | null = licenseMock) => { }; describe('SLO Edit Page', () => { - const mockCreate = jest.fn(); + const mockCreate = jest.fn(() => Promise.resolve({ id: 'mock-slo-id' })); const mockUpdate = jest.fn(); + const mockCreateRule = jest.fn(); const history = createBrowserHistory(); @@ -163,6 +167,13 @@ describe('SLO Edit Page', () => { mutateAsync: mockCreate, }); + useCreateRuleMock.mockReturnValue({ + isLoading: false, + isSuccess: false, + isError: false, + mutateAsync: mockCreateRule, + }); + useUpdateSloMock.mockReturnValue({ isLoading: false, isSuccess: false, @@ -393,7 +404,7 @@ describe('SLO Edit Page', () => { }); describe('when submitting has completed successfully', () => { - it('navigates to the SLO List page when checkbox to create new rule is not checked', async () => { + it('navigates to the SLO List page', async () => { const slo = buildSlo(); jest.spyOn(Router, 'useParams').mockReturnValue({ sloId: '123' }); @@ -414,52 +425,6 @@ describe('SLO Edit Page', () => { expect(mockNavigate).toBeCalledWith(mockBasePathPrepend(paths.observability.slos)); }); }); - - it('navigates to the SLO Edit page when checkbox to create new rule is checked', async () => { - const slo = buildSlo(); - - jest.spyOn(Router, 'useParams').mockReturnValue({ sloId: '123' }); - jest - .spyOn(Router, 'useLocation') - .mockReturnValue({ pathname: '/slos/123/edit', search: '', state: '', hash: '' }); - - useFetchSloMock.mockReturnValue({ isLoading: false, data: slo }); - - const { getByTestId } = render(); - - expect(getByTestId('sloFormSubmitButton')).toBeEnabled(); - - await waitFor(() => { - fireEvent.click(getByTestId('createNewRuleCheckbox')); - fireEvent.click(getByTestId('sloFormSubmitButton')); - }); - - await waitFor(() => { - expect(mockNavigate).toBeCalledWith( - mockBasePathPrepend(`${paths.observability.sloEdit(slo.id)}?create-rule=true`) - ); - }); - }); - - it('opens the Add Rule Flyout when visiting an existing SLO with search params set', async () => { - const slo = buildSlo(); - - jest.spyOn(Router, 'useParams').mockReturnValue({ sloId: '123' }); - jest.spyOn(Router, 'useLocation').mockReturnValue({ - pathname: '/slos/123/edit', - search: 'create-rule=true', - state: '', - hash: '', - }); - - useFetchSloMock.mockReturnValue({ isLoading: false, data: slo }); - - const { getByTestId } = render(); - - await waitFor(() => { - expect(getByTestId('add-rule-flyout')).toBeTruthy(); - }); - }); }); }); }); diff --git a/x-pack/plugins/observability/public/pages/slos/components/badges/slo_badges.tsx b/x-pack/plugins/observability/public/pages/slos/components/badges/slo_badges.tsx index 9ff1e3c14a2b2..d801bd848ce9b 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/badges/slo_badges.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/badges/slo_badges.tsx @@ -10,6 +10,7 @@ import { EuiFlexGroup, EuiSkeletonRectangle } from '@elastic/eui'; import { SLOWithSummaryResponse } from '@kbn/slo-schema'; import { Rule } from '@kbn/triggers-actions-ui-plugin/public'; +import { SloTagsList } from '../common/slo_tags_list'; import { SloIndicatorTypeBadge } from './slo_indicator_type_badge'; import { SloStatusBadge } from '../../../../components/slo/slo_status_badge'; import { SloActiveAlertsBadge } from '../../../../components/slo/slo_status_badge/slo_active_alerts_badge'; @@ -41,11 +42,12 @@ export function SloBadges({ ) : ( <> + - + )} diff --git a/x-pack/plugins/observability/public/pages/slos/components/badges/slo_indicator_type_badge.tsx b/x-pack/plugins/observability/public/pages/slos/components/badges/slo_indicator_type_badge.tsx index 60fceb4481b91..bf33f22e25221 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/badges/slo_indicator_type_badge.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/badges/slo_indicator_type_badge.tsx @@ -15,6 +15,7 @@ import { } from '@kbn/slo-schema'; import { euiLightVars } from '@kbn/ui-theme'; import React from 'react'; +import { useUrlSearchState } from '../../hooks/use_url_search_state'; import { useKibana } from '../../../../utils/kibana_react'; import { convertSliApmParamsToApmAppDeeplinkUrl } from '../../../../utils/slo/convert_sli_apm_params_to_apm_app_deeplink_url'; import { toIndicatorTypeLabel } from '../../../../utils/slo/labels'; @@ -30,6 +31,8 @@ export function SloIndicatorTypeBadge({ slo, color }: Props) { http: { basePath }, } = useKibana().services; + const { onStateChange } = useUrlSearchState(); + const handleNavigateToApm = () => { const url = convertSliApmParamsToApmAppDeeplinkUrl(slo); if (url) { @@ -40,7 +43,21 @@ export function SloIndicatorTypeBadge({ slo, color }: Props) { return ( <> - + { + onStateChange({ + kqlQuery: `slo.indicator.type: ${slo.indicator.type}`, + }); + }} + onClickAriaLabel={i18n.translate( + 'xpack.observability.slo.indicatorTypeBadge.clickToFilter', + { + defaultMessage: 'Click to filter by {indicatorType} SLOs', + values: { indicatorType: toIndicatorTypeLabel(slo.indicator.type) }, + } + )} + > {toIndicatorTypeLabel(slo.indicator.type)} diff --git a/x-pack/plugins/observability/public/pages/slos/components/card_view/slo_card_item_badges.tsx b/x-pack/plugins/observability/public/pages/slos/components/card_view/slo_card_item_badges.tsx index 1fec95eb98f73..66effe39c48bc 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/card_view/slo_card_item_badges.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/card_view/slo_card_item_badges.tsx @@ -6,10 +6,12 @@ */ import { SLOWithSummaryResponse } from '@kbn/slo-schema'; -import React from 'react'; +import React, { useCallback } from 'react'; import { Rule } from '@kbn/triggers-actions-ui-plugin/public'; import styled from 'styled-components'; import { EuiFlexGroup } from '@elastic/eui'; +import { SloTagsList } from '../common/slo_tags_list'; +import { useUrlSearchState } from '../../hooks/use_url_search_state'; import { LoadingBadges } from '../badges/slo_badges'; import { SloIndicatorTypeBadge } from '../badges/slo_indicator_type_badge'; import { SloTimeWindowBadge } from '../badges/slo_time_window_badge'; @@ -31,6 +33,16 @@ const Container = styled.div` `; export function SloCardItemBadges({ slo, activeAlerts, rules, handleCreateRule }: Props) { + const { onStateChange } = useUrlSearchState(); + + const onTagClick = useCallback( + (tag: string) => { + onStateChange({ + kqlQuery: `slo.tags: "${tag}"`, + }); + }, + [onStateChange] + ); return ( @@ -42,6 +54,13 @@ export function SloCardItemBadges({ slo, activeAlerts, rules, handleCreateRule } + )} diff --git a/x-pack/plugins/observability/public/pages/slos/components/card_view/slos_card_view.tsx b/x-pack/plugins/observability/public/pages/slos/components/card_view/slos_card_view.tsx index 61f9cb4745be4..8397a8ce99472 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/card_view/slos_card_view.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/card_view/slos_card_view.tsx @@ -5,7 +5,6 @@ * 2.0. */ -import React from 'react'; import { EuiFlexGrid, EuiFlexItem, @@ -13,39 +12,45 @@ import { EuiSkeletonText, useIsWithinBreakpoints, } from '@elastic/eui'; -import { SLOWithSummaryResponse, ALL_VALUE } from '@kbn/slo-schema'; import { EuiFlexGridProps } from '@elastic/eui/src/components/flex/flex_grid'; -import { ActiveAlerts } from '../../../../hooks/slo/active_alerts'; -import type { UseFetchRulesForSloResponse } from '../../../../hooks/slo/use_fetch_rules_for_slo'; +import { ALL_VALUE, SLOWithSummaryResponse } from '@kbn/slo-schema'; +import React from 'react'; +import { useFetchActiveAlerts } from '../../../../hooks/slo/use_fetch_active_alerts'; import { useFetchHistoricalSummary } from '../../../../hooks/slo/use_fetch_historical_summary'; +import { useFetchRulesForSlo } from '../../../../hooks/slo/use_fetch_rules_for_slo'; import { SloCardItem } from './slo_card_item'; export interface Props { sloList: SLOWithSummaryResponse[]; loading: boolean; error: boolean; - activeAlertsBySlo: ActiveAlerts; - rulesBySlo?: UseFetchRulesForSloResponse['data']; } const useColumns = () => { const isMobile = useIsWithinBreakpoints(['xs', 's']); const isMedium = useIsWithinBreakpoints(['m']); - const isLarge = useIsWithinBreakpoints(['l', 'xl']); + const isXLarge = useIsWithinBreakpoints(['xl']); switch (true) { case isMobile: return 1; case isMedium: return 3; - case isLarge: + case isXLarge: return 4; default: return 3; } }; -export function SloListCardView({ sloList, loading, error, rulesBySlo, activeAlertsBySlo }: Props) { +export function SloListCardView({ sloList, loading, error }: Props) { + const sloIdsAndInstanceIds = sloList.map( + (slo) => [slo.id, slo.instanceId ?? ALL_VALUE] as [string, string] + ); + const { data: activeAlertsBySlo } = useFetchActiveAlerts({ sloIdsAndInstanceIds }); + const { data: rulesBySlo } = useFetchRulesForSlo({ + sloIds: sloIdsAndInstanceIds.map((item) => item[0]), + }); const { isLoading: historicalSummaryLoading, data: historicalSummaries = [] } = useFetchHistoricalSummary({ list: sloList.map((slo) => ({ sloId: slo.id, instanceId: slo.instanceId ?? ALL_VALUE })), diff --git a/x-pack/plugins/observability/public/pages/slos/components/common/slo_tags_list.tsx b/x-pack/plugins/observability/public/pages/slos/components/common/slo_tags_list.tsx new file mode 100644 index 0000000000000..66a415a8946ad --- /dev/null +++ b/x-pack/plugins/observability/public/pages/slos/components/common/slo_tags_list.tsx @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useCallback } from 'react'; +import { TagsList } from '@kbn/observability-shared-plugin/public'; +import type { TagsListProps } from '@kbn/observability-shared-plugin/public'; +import { useUrlSearchState } from '../../hooks/use_url_search_state'; + +export function SloTagsList(props: TagsListProps) { + const { onStateChange } = useUrlSearchState(); + + const onTagClick = useCallback( + (tag: string) => { + onStateChange({ + kqlQuery: `slo.tags: "${tag}"`, + }); + }, + [onStateChange] + ); + + return ; +} diff --git a/x-pack/plugins/observability/public/pages/slos/components/common/sort_by_select.tsx b/x-pack/plugins/observability/public/pages/slos/components/common/sort_by_select.tsx index fe56f128b6eba..1119c275180f2 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/common/sort_by_select.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/common/sort_by_select.tsx @@ -5,64 +5,130 @@ * 2.0. */ -import { EuiSelect } from '@elastic/eui'; +import { EuiPanel, EuiSelectableOption, EuiText } from '@elastic/eui'; +import { EuiSelectableOptionCheckedType } from '@elastic/eui/src/components/selectable/selectable_option'; import { i18n } from '@kbn/i18n'; -import React from 'react'; -import { EuiSelectOption } from '@elastic/eui/src/components/form/select/select'; -import { SortField } from '../slo_list_search_bar'; -import { SearchState } from '../../hooks/use_url_search_state'; +import React, { useState } from 'react'; +import type { SearchState } from '../../hooks/use_url_search_state'; +import type { Option } from '../slo_context_menu'; +import { ContextMenuItem, SLOContextMenu } from '../slo_context_menu'; +import type { SortField } from '../slo_list_search_bar'; -interface Props { - initialState: SearchState; - loading: boolean; +export interface Props { onStateChange: (newState: Partial) => void; + state: SearchState; } -export function SortBySelect({ initialState, onStateChange, loading }: Props) { - return ( - { - onStateChange({ - page: 0, - sort: { by: evt.target.value as SortField, direction: initialState.sort.direction }, +export type Item = EuiSelectableOption & { + label: string; + type: T; + checked?: EuiSelectableOptionCheckedType; +}; + +export function SLOSortBy({ state, onStateChange }: Props) { + const [isSortByPopoverOpen, setIsSortByPopoverOpen] = useState(false); + const sortBy = state.sort.by; + + const handleChangeSortBy = ({ value, label }: { value: SortField; label: string }) => { + onStateChange({ + page: 0, + sort: { by: value, direction: state.sort.direction }, + }); + }; + + const sortByOptions: Option[] = [ + { + label: i18n.translate('xpack.observability.slo.list.sortBy.sliValue', { + defaultMessage: 'SLI value', + }), + checked: sortBy === 'sli_value', + value: 'sli_value', + onClick: () => { + handleChangeSortBy({ + value: 'sli_value', + label: i18n.translate('xpack.observability.slo.list.sortBy.sliValue', { + defaultMessage: 'SLI value', + }), + }); + }, + }, + { + label: i18n.translate('xpack.observability.slo.list.sortBy.sloStatus', { + defaultMessage: 'SLO status', + }), + checked: sortBy === 'status', + value: 'status', + onClick: () => { + handleChangeSortBy({ + value: 'status', + label: i18n.translate('xpack.observability.slo.list.sortBy.sloStatus', { + defaultMessage: 'SLO status', + }), + }); + }, + }, + { + label: i18n.translate('xpack.observability.slo.list.sortBy.errorBudgetConsumed', { + defaultMessage: 'Error budget consumed', + }), + checked: sortBy === 'error_budget_consumed', + value: 'error_budget_consumed', + onClick: () => { + handleChangeSortBy({ + value: 'error_budget_consumed', + label: i18n.translate('xpack.observability.slo.list.sortBy.errorBudgetConsumed', { + defaultMessage: 'Error budget consumed', + }), + }); + }, + }, + { + label: i18n.translate('xpack.observability.slo.list.sortBy.errorBudgetRemaining', { + defaultMessage: 'Error budget remaining', + }), + checked: sortBy === 'error_budget_remaining', + value: 'error_budget_remaining', + onClick: () => { + handleChangeSortBy({ + value: 'error_budget_remaining', + label: i18n.translate('xpack.observability.slo.list.sortBy.errorBudgetRemaining', { + defaultMessage: 'Error budget remaining', + }), }); - }} + }, + }, + ]; + + const groupLabel = sortByOptions.find((option) => option.value === sortBy)?.label || 'Default'; + + const items = [ + + +

{SORT_BY_LABEL}

+
+
, + + ...sortByOptions.map((option) => ( + setIsSortByPopoverOpen(false)} + key={option.value} + /> + )), + ]; + + return ( + ); } -const SORT_OPTIONS: EuiSelectOption[] = [ - { - text: i18n.translate('xpack.observability.slo.list.sortBy.sliValue', { - defaultMessage: 'SLI value', - }), - value: 'sli_value', - }, - { - text: i18n.translate('xpack.observability.slo.list.sortBy.sloStatus', { - defaultMessage: 'SLO status', - }), - value: 'status', - }, - { - text: i18n.translate('xpack.observability.slo.list.sortBy.errorBudgetConsumed', { - defaultMessage: 'Error budget consumed', - }), - value: 'error_budget_consumed', - }, - { - text: i18n.translate('xpack.observability.slo.list.sortBy.errorBudgetRemaining', { - defaultMessage: 'Error budget remaining', - }), - value: 'error_budget_remaining', - }, -]; - const SORT_BY_LABEL = i18n.translate('xpack.observability.slo.list.sortByTypeLabel', { defaultMessage: 'Sort by', }); diff --git a/x-pack/plugins/observability/public/pages/slos/components/compact_view/slo_list_compact_view.tsx b/x-pack/plugins/observability/public/pages/slos/components/compact_view/slo_list_compact_view.tsx index 177be6f062923..3a07bc3ac08b6 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/compact_view/slo_list_compact_view.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/compact_view/slo_list_compact_view.tsx @@ -19,6 +19,7 @@ import { i18n } from '@kbn/i18n'; import { ALL_VALUE, SLOWithSummaryResponse } from '@kbn/slo-schema'; import { useQueryClient } from '@tanstack/react-query'; import React, { useState } from 'react'; +import { SloTagsList } from '../common/slo_tags_list'; import { useCloneSlo } from '../../../../hooks/slo/use_clone_slo'; import { rulesLocatorID, sloFeatureId } from '../../../../../common'; import { SLO_BURN_RATE_RULE_TYPE_ID } from '../../../../../common/constants'; @@ -233,7 +234,7 @@ export function SloListCompactView({ sloList, loading, error }: Props) { { field: 'name', name: 'Name', - width: '20%', + width: '15%', truncateText: { lines: 2 }, 'data-test-subj': 'sloItem', render: (_, slo: SLOWithSummaryResponse) => { @@ -258,6 +259,11 @@ export function SloListCompactView({ sloList, loading, error }: Props) { ); }, }, + { + field: 'tags', + name: 'Tags', + render: (tags: string[]) => , + }, { field: 'instance', name: 'Instance', @@ -379,6 +385,7 @@ export function SloListCompactView({ sloList, loading, error }: Props) { columns={columns} loading={loading} noItemsMessage={loading ? LOADING_SLOS_LABEL : NO_SLOS_FOUND} + tableLayout="auto" /> {sloToAddRule ? ( + {i18n.translate('xpack.observability.slo.groupList.emptyMessage', { + defaultMessage: 'There are no results for your criteria.', + })} + + ); +} diff --git a/x-pack/plugins/observability/public/pages/slos/components/grouped_slos/group_list_error.tsx b/x-pack/plugins/observability/public/pages/slos/components/grouped_slos/group_list_error.tsx new file mode 100644 index 0000000000000..d67ef0548a949 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/slos/components/grouped_slos/group_list_error.tsx @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiEmptyPrompt } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +export function SloGroupListError() { + return ( + + {i18n.translate('xpack.observability.slo.groupList.errorTitle', { + defaultMessage: 'Unable to load SLO groups', + })} + + } + body={ +

+ {i18n.translate('xpack.observability.slo.groupList.errorMessage', { + defaultMessage: + 'There was an error loading the SLO groups. Contact your administrator for help.', + })} +

+ } + /> + ); +} diff --git a/x-pack/plugins/observability/public/pages/slos/components/grouped_slos/group_list_view.tsx b/x-pack/plugins/observability/public/pages/slos/components/grouped_slos/group_list_view.tsx new file mode 100644 index 0000000000000..a9f39c4687173 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/slos/components/grouped_slos/group_list_view.tsx @@ -0,0 +1,224 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + EuiAccordion, + EuiBadge, + EuiFlexGroup, + EuiFlexItem, + EuiLink, + EuiPanel, + EuiSpacer, + EuiTablePagination, + EuiText, + EuiTextColor, + EuiTitle, + EuiToolTip, +} from '@elastic/eui'; +import { Filter } from '@kbn/es-query'; +import { i18n } from '@kbn/i18n'; +import React, { memo, useState } from 'react'; +import { GroupSummary } from '@kbn/slo-schema'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { CoreStart } from '@kbn/core-lifecycle-browser'; +import { paths } from '../../../../../common/locators/paths'; +import { useFetchSloList } from '../../../../hooks/slo/use_fetch_slo_list'; +import { SLI_OPTIONS } from '../../../slo_edit/constants'; +import { useSloFormattedSLIValue } from '../../hooks/use_slo_summary'; +import { SlosView } from '../slos_view'; +import type { SortDirection } from '../slo_list_search_bar'; +import { SLOView } from '../toggle_slo_view'; + +interface Props { + group: string; + kqlQuery: string; + sloView: SLOView; + sort: string; + direction: SortDirection; + groupBy: string; + summary: GroupSummary; + filters: Filter[]; +} + +export function GroupListView({ + group, + kqlQuery, + sloView, + sort, + direction, + groupBy, + summary, + filters, +}: Props) { + const query = kqlQuery ? `"${groupBy}": (${group}) and ${kqlQuery}` : `"${groupBy}": ${group}`; + let groupName = group.toLowerCase(); + if (groupBy === 'slo.indicator.type') { + groupName = SLI_OPTIONS.find((option) => option.value === group)?.text ?? group; + } + + const [page, setPage] = useState(0); + const [accordionState, setAccordionState] = useState<'open' | 'closed'>('closed'); + const onToggle = (isOpen: boolean) => { + const newState = isOpen ? 'open' : 'closed'; + setAccordionState(newState); + }; + const isAccordionOpen = accordionState === 'open'; + + const { + http: { basePath }, + } = useKibana().services; + + const [itemsPerPage, setItemsPerPage] = useState(10); + const { + isLoading, + isRefetching, + isError, + data: sloList, + } = useFetchSloList({ + kqlQuery: query, + sortBy: sort, + sortDirection: direction, + perPage: itemsPerPage, + page: page + 1, + filters, + disabled: !isAccordionOpen, + }); + const { results = [], total = 0 } = sloList ?? {}; + + const handlePageClick = (pageNumber: number) => { + setPage(pageNumber); + }; + + const worstSLI = useSloFormattedSLIValue(summary.worst.sliValue); + + return ( + <> + + + + + + +

{groupName}

+
+
+ + ({summary.total}) + +
+ } + extraAction={ + + {summary.violated > 0 && ( + + + {i18n.translate('xpack.observability.slo.group.totalViolated', { + defaultMessage: '{total} Violated', + values: { + total: summary.violated, + }, + })} + + + )} + + + {i18n.translate('xpack.observability.slo.group.totalHealthy', { + defaultMessage: '{total} Healthy', + values: { + total: summary.healthy, + }, + })} + + + + + + {i18n.translate( + 'xpack.observability.slo.group.totalSloViolatedTooltip', + { + defaultMessage: 'SLO: {name}', + values: { + name: summary.worst.slo?.name, + }, + } + )} + + + {i18n.translate( + 'xpack.observability.slo.group.totalSloViolatedTooltip.instance', + { + defaultMessage: 'Instance: {instance}', + values: { + instance: summary.worst.slo?.instanceId, + }, + } + )} + + + } + > + + {i18n.translate('xpack.observability.slo.group.worstPerforming', { + defaultMessage: 'Worst performing: ', + })} + + {worstSLI} + + + + + + } + id={group} + initialIsOpen={false} + > + {isAccordionOpen && ( + <> + + + + setItemsPerPage(perPage)} + /> + + )} + +
+ + + + + ); +} + +const MemoEuiAccordion = memo(EuiAccordion); diff --git a/x-pack/plugins/observability/public/pages/slos/components/grouped_slos/group_view.test.tsx b/x-pack/plugins/observability/public/pages/slos/components/grouped_slos/group_view.test.tsx new file mode 100644 index 0000000000000..ff35a72f0ce50 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/slos/components/grouped_slos/group_view.test.tsx @@ -0,0 +1,225 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import { render } from '../../../../utils/test_helper'; +import { useFetchSloGroups } from '../../../../hooks/slo/use_fetch_slo_groups'; +import { useFetchSloList } from '../../../../hooks/slo/use_fetch_slo_list'; +import { DEFAULT_SLO_GROUPS_PAGE_SIZE } from '../../../../../common/slo/constants'; + +import { useUrlSearchState } from '../../hooks/use_url_search_state'; +import { GroupView } from './group_view'; + +jest.mock('../../../../hooks/slo/use_fetch_slo_groups'); +jest.mock('../../hooks/use_url_search_state'); +jest.mock('../../../../hooks/slo/use_fetch_slo_list'); + +const useFetchSloGroupsMock = useFetchSloGroups as jest.Mock; +const useUrlSearchStateMock = useUrlSearchState as jest.Mock; +const useFetchSloListMock = useFetchSloList as jest.Mock; + +describe('Group View', () => { + beforeEach(() => { + useUrlSearchStateMock.mockImplementation(() => ({ + state: { + page: 0, + perPage: DEFAULT_SLO_GROUPS_PAGE_SIZE, + }, + })); + + useFetchSloListMock.mockReturnValue({ + isLoading: false, + isError: false, + isSuccess: true, + data: { + page: 0, + perPage: 10, + total: 0, + }, + }); + }); + + it('should show error', async () => { + useFetchSloGroupsMock.mockReturnValue({ + isError: true, + isLoading: false, + }); + const { queryByTestId, getByTestId } = render( + + ); + + expect(queryByTestId('sloGroupView')).toBeNull(); + expect(getByTestId('sloGroupListError')).toBeInTheDocument(); + }); + + it('should show no results', async () => { + useFetchSloGroupsMock.mockReturnValue({ + isError: false, + isLoading: false, + data: { + page: 0, + perPage: 10, + total: 0, + results: [], + }, + }); + + const { queryByTestId, getByTestId } = render( + + ); + + expect(queryByTestId('sloGroupView')).toBeNull(); + expect(getByTestId('sloGroupListEmpty')).toBeInTheDocument(); + }); + + it('should show loading indicator', async () => { + useFetchSloGroupsMock.mockReturnValue({ + isLoading: true, + }); + + const { queryByTestId, getByTestId } = render( + + ); + expect(queryByTestId('sloGroupView')).toBeNull(); + expect(getByTestId('sloGroupListLoading')).toBeInTheDocument(); + }); + + describe('group by tags', () => { + it('should render slo groups grouped by tags', async () => { + useFetchSloGroupsMock.mockReturnValue({ + isLoading: false, + isError: false, + isSuccess: true, + data: { + page: 0, + perPage: 10, + total: 3, + results: [ + { + group: 'production', + groupBy: 'slo.tags', + summary: { total: 3, worst: 0.95, healthy: 2, violated: 1, degrading: 0, noData: 0 }, + }, + { + group: 'something', + groupBy: 'slo.tags', + summary: { total: 1, worst: 0.9, healthy: 0, violated: 1, degrading: 0, noData: 0 }, + }, + { + group: 'anything', + groupBy: 'slo.tags', + summary: { total: 2, worst: 0.85, healthy: 1, violated: 0, degrading: 0, noData: 1 }, + }, + ], + }, + }); + const { queryAllByTestId, getByTestId } = render( + + ); + expect(getByTestId('sloGroupView')).toBeInTheDocument(); + expect(useFetchSloGroups).toHaveBeenCalled(); + expect(useFetchSloGroups).toHaveBeenCalledWith({ + groupBy: 'slo.tags', + kqlQuery: '', + page: 1, + perPage: DEFAULT_SLO_GROUPS_PAGE_SIZE, + }); + expect(queryAllByTestId('sloGroupViewPanel').length).toEqual(3); + }); + + it('should render slo groups filtered by selected tags', async () => { + useUrlSearchStateMock.mockImplementation(() => ({ + state: { + tags: { + included: ['production'], + excluded: [], + }, + page: 0, + perPage: DEFAULT_SLO_GROUPS_PAGE_SIZE, + }, + })); + useFetchSloGroupsMock.mockReturnValue({ + isLoading: false, + isError: false, + isSuccess: true, + data: { + page: 0, + perPage: 10, + total: 1, + results: [ + { + group: 'production', + groupBy: 'tags', + summary: { total: 3, worst: 0.95, healthy: 2, violated: 1, degrading: 0, noData: 0 }, + }, + ], + }, + }); + + const { queryAllByTestId } = render( + + ); + expect(useFetchSloGroups).toHaveBeenCalled(); + expect(useFetchSloGroups).toHaveBeenCalledWith({ + groupBy: 'slo.tags', + kqlQuery: '', + page: 1, + perPage: DEFAULT_SLO_GROUPS_PAGE_SIZE, + }); + expect(queryAllByTestId('sloGroupViewPanel').length).toEqual(1); + }); + }); + + describe('group by status', () => { + it('should render slo groups grouped by status', async () => { + const { getByTestId } = render( + + ); + expect(getByTestId('sloGroupView')).toBeInTheDocument(); + expect(useFetchSloGroups).toHaveBeenCalled(); + expect(useFetchSloGroups).toHaveBeenCalledWith({ + groupBy: 'status', + kqlQuery: '', + page: 1, + perPage: DEFAULT_SLO_GROUPS_PAGE_SIZE, + }); + }); + }); + + describe('group by SLI indicator type', () => { + it('should render slo groups grouped by indicator type', async () => { + const { getByTestId } = render( + + ); + expect(getByTestId('sloGroupView')).toBeInTheDocument(); + expect(useFetchSloGroups).toHaveBeenCalled(); + expect(useFetchSloGroups).toHaveBeenCalledWith({ + groupBy: 'slo.indicator.type', + kqlQuery: '', + page: 1, + perPage: DEFAULT_SLO_GROUPS_PAGE_SIZE, + }); + }); + }); +}); diff --git a/x-pack/plugins/observability/public/pages/slos/components/grouped_slos/group_view.tsx b/x-pack/plugins/observability/public/pages/slos/components/grouped_slos/group_view.tsx new file mode 100644 index 0000000000000..4941606db7edc --- /dev/null +++ b/x-pack/plugins/observability/public/pages/slos/components/grouped_slos/group_view.tsx @@ -0,0 +1,94 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { EuiEmptyPrompt, EuiFlexItem, EuiLoadingSpinner, EuiTablePagination } from '@elastic/eui'; +import React from 'react'; +import { useFetchSloGroups } from '../../../../hooks/slo/use_fetch_slo_groups'; +import { useUrlSearchState } from '../../hooks/use_url_search_state'; +import type { SortDirection } from '../slo_list_search_bar'; +import { SLOView } from '../toggle_slo_view'; +import { SloGroupListEmpty } from './group_list_empty'; +import { SloGroupListError } from './group_list_error'; +import { GroupListView } from './group_list_view'; + +interface Props { + groupBy: string; + kqlQuery: string; + sloView: SLOView; + sort: string; + direction: SortDirection; +} + +export function GroupView({ kqlQuery, sloView, sort, direction, groupBy }: Props) { + const { state, onStateChange } = useUrlSearchState(); + const { tagsFilter, statusFilter, filters, page, perPage, lastRefresh } = state; + + const { data, isLoading, isError } = useFetchSloGroups({ + perPage, + page: page + 1, + groupBy, + kqlQuery, + tagsFilter, + statusFilter, + filters, + lastRefresh, + }); + const { results = [], total = 0 } = data ?? {}; + const handlePageClick = (pageNumber: number) => { + onStateChange({ page: pageNumber }); + }; + + if (isLoading) { + return ( + } + /> + ); + } + + if (!isLoading && !isError && results.length === 0) { + return ; + } + + if (!isLoading && isError) { + return ; + } + return ( + + {results && + results.map((result) => ( + + ))} + + {total > 0 ? ( + + { + onStateChange({ perPage: newPerPage }); + }} + /> + + ) : null} + + ); +} diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_context_menu.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_context_menu.tsx new file mode 100644 index 0000000000000..0ad5c4b4d827e --- /dev/null +++ b/x-pack/plugins/observability/public/pages/slos/components/slo_context_menu.tsx @@ -0,0 +1,124 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import { + EuiButtonEmpty, + EuiContextMenuPanel, + EuiContextMenuItem, + EuiFlexGroup, + EuiFlexItem, + EuiPopover, + EuiSelectableOption, + useGeneratedHtmlId, + EuiTitle, +} from '@elastic/eui'; + +import { EuiSelectableOptionCheckedType } from '@elastic/eui/src/components/selectable/selectable_option'; + +export interface Option { + label: string; + value: string; + checked: boolean; + defaultSortOrder?: string; + onClick: () => void; +} + +export interface Props { + id: string; + isPopoverOpen: boolean; + setIsPopoverOpen: (isPopoverOpen: boolean) => void; + items: JSX.Element[]; + selected: string; + label: string; +} + +export type Item = EuiSelectableOption & { + label: string; + type: T; + checked?: EuiSelectableOptionCheckedType; +}; + +export function SLOContextMenu({ + id, + isPopoverOpen, + label, + items, + selected, + setIsPopoverOpen, +}: Props) { + const singleContextMenuPopoverId = useGeneratedHtmlId({ + prefix: 'singleContextMenuPopover', + }); + + const handleTogglePopover = () => { + setIsPopoverOpen(!isPopoverOpen); + }; + + const button = ( + + {selected} + + ); + + return ( + + + + + + {label} + + + + setIsPopoverOpen(false)} + panelPaddingSize="none" + anchorPosition="downLeft" + > + + + + + + + ); +} + +export function ContextMenuItem({ + option, + onClosePopover, +}: { + option: Option; + onClosePopover: () => void; +}) { + const getIconType = (checked: boolean) => { + return checked ? 'check' : 'empty'; + }; + + return ( + { + onClosePopover(); + option.onClick(); + }} + > + {option.label} + + ); +} diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_list.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_list.tsx index 0b16bcd2bf0d2..19617273659b1 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/slo_list.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/slo_list.tsx @@ -12,10 +12,11 @@ import { useFetchSloList } from '../../../hooks/slo/use_fetch_slo_list'; import { SearchState, useUrlSearchState } from '../hooks/use_url_search_state'; import { SlosView } from './slos_view'; import { ToggleSLOView } from './toggle_slo_view'; +import { GroupView } from './grouped_slos/group_view'; export function SloList() { - const { state, store: storeState } = useUrlSearchState(); - const { view, page, perPage, kqlQuery, filters, tagsFilter, statusFilter } = state; + const { state, onStateChange: storeState } = useUrlSearchState(); + const { view, page, perPage, kqlQuery, filters, tagsFilter, statusFilter, groupBy } = state; const { isLoading, @@ -33,7 +34,6 @@ export function SloList() { sortDirection: state.sort.direction, lastRefresh: state.lastRefresh, }); - const { results = [], total = 0 } = sloList ?? {}; const isCreatingSlo = Boolean(useIsMutating(['creatingSlo'])); @@ -52,34 +52,46 @@ export function SloList() { sloList={sloList} sloView={view} onChangeView={(newView) => onStateChange({ view: newView })} - loading={isLoading || isCreatingSlo || isCloningSlo || isUpdatingSlo || isDeletingSlo} onStateChange={onStateChange} - initialState={state} + state={state} + loading={isLoading || isCreatingSlo || isCloningSlo || isUpdatingSlo || isDeletingSlo} /> - - - {total > 0 ? ( - - { - onStateChange({ page: newPage }); - }} - itemsPerPage={perPage} - itemsPerPageOptions={[10, 25, 50, 100]} - onChangeItemsPerPage={(newPerPage) => { - storeState({ perPage: newPerPage, page: 0 }); - }} + {groupBy === 'ungrouped' && ( + <> + - - ) : null} + {total > 0 ? ( + + { + onStateChange({ page: newPage }); + }} + itemsPerPage={perPage} + itemsPerPageOptions={[10, 25, 50, 100]} + onChangeItemsPerPage={(newPerPage) => { + storeState({ perPage: newPerPage, page: 0 }); + }} + /> + + ) : null} + + )} + {groupBy !== 'ungrouped' && ( + + )} ); } diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_list_group_by.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_list_group_by.tsx new file mode 100644 index 0000000000000..aeefcb84bc3af --- /dev/null +++ b/x-pack/plugins/observability/public/pages/slos/components/slo_list_group_by.tsx @@ -0,0 +1,112 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { EuiPanel, EuiSelectableOption, EuiText } from '@elastic/eui'; +import { EuiSelectableOptionCheckedType } from '@elastic/eui/src/components/selectable/selectable_option'; +import { i18n } from '@kbn/i18n'; +import React, { useState } from 'react'; +import type { SearchState } from '../hooks/use_url_search_state'; +import type { Option } from './slo_context_menu'; +import { ContextMenuItem, SLOContextMenu } from './slo_context_menu'; + +export type GroupByField = 'ungrouped' | 'slo.tags' | 'status' | 'slo.indicator.type'; +export interface Props { + onStateChange: (newState: Partial) => void; + state: SearchState; +} + +export type Item = EuiSelectableOption & { + label: string; + type: T; + checked?: EuiSelectableOptionCheckedType; +}; + +export function SloGroupBy({ onStateChange, state }: Props) { + const [isGroupByPopoverOpen, setIsGroupByPopoverOpen] = useState(false); + const groupBy = state.groupBy; + + const handleChangeGroupBy = (value: GroupByField) => { + onStateChange({ + page: 0, + groupBy: value, + }); + }; + const groupByOptions: Option[] = [ + { + label: NONE_LABEL, + checked: groupBy === 'ungrouped', + value: 'ungrouped', + onClick: () => { + handleChangeGroupBy('ungrouped'); + }, + }, + { + label: i18n.translate('xpack.observability.slo.list.groupBy.tags', { + defaultMessage: 'Tags', + }), + checked: groupBy === 'slo.tags', + value: 'slo.tags', + onClick: () => { + handleChangeGroupBy('slo.tags'); + }, + }, + { + label: i18n.translate('xpack.observability.slo.list.groupBy.status', { + defaultMessage: 'Status', + }), + checked: groupBy === 'status', + value: 'status', + onClick: () => { + handleChangeGroupBy('status'); + }, + }, + { + label: i18n.translate('xpack.observability.slo.list.groupBy.sliType', { + defaultMessage: 'SLI type', + }), + checked: groupBy === 'slo.indicator.type', + value: 'slo.indicator.type', + onClick: () => { + handleChangeGroupBy('slo.indicator.type'); + }, + }, + ]; + + const items = [ + + +

{GROUP_TITLE}

+
+
, + + ...groupByOptions.map((option) => ( + setIsGroupByPopoverOpen(false)} + key={option.value} + /> + )), + ]; + + return ( + option.value === groupBy)?.label || NONE_LABEL} + isPopoverOpen={isGroupByPopoverOpen} + setIsPopoverOpen={setIsGroupByPopoverOpen} + label={GROUP_TITLE} + /> + ); +} + +export const NONE_LABEL = i18n.translate('xpack.observability.slo.list.groupBy.sliIndicator', { + defaultMessage: 'None', +}); + +export const GROUP_TITLE = i18n.translate('xpack.observability.slo.groupPopover.group.title', { + defaultMessage: 'Group by', +}); diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_list_items.stories.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_list_items.stories.tsx deleted file mode 100644 index 1c6cdfe51700c..0000000000000 --- a/x-pack/plugins/observability/public/pages/slos/components/slo_list_items.stories.tsx +++ /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 React from 'react'; -import { ComponentStory } from '@storybook/react'; - -import { ActiveAlerts } from '../../../hooks/slo/active_alerts'; -import { KibanaReactStorybookDecorator } from '../../../utils/kibana_react.storybook_decorator'; -import { SloListItems as Component, Props } from './slo_list_items'; -import { sloList } from '../../../data/slo/slo'; - -export default { - component: Component, - title: 'app/SLO/ListPage/SloListItems', - decorators: [KibanaReactStorybookDecorator], -}; - -const Template: ComponentStory = (props: Props) => ; - -const defaultProps: Props = { - sloList: sloList.results, - activeAlertsBySlo: new ActiveAlerts(), - loading: false, - error: false, -}; - -export const SloListItems = Template.bind({}); -SloListItems.args = defaultProps; diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_list_items.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_list_items.tsx deleted file mode 100644 index 75fa9632d15bd..0000000000000 --- a/x-pack/plugins/observability/public/pages/slos/components/slo_list_items.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 { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { ALL_VALUE, SLOWithSummaryResponse } from '@kbn/slo-schema'; -import React from 'react'; -import { ActiveAlerts } from '../../../hooks/slo/active_alerts'; -import { useFetchHistoricalSummary } from '../../../hooks/slo/use_fetch_historical_summary'; -import { UseFetchRulesForSloResponse } from '../../../hooks/slo/use_fetch_rules_for_slo'; -import { SloListItem } from './slo_list_item'; -import { SloListCompactView } from './compact_view/slo_list_compact_view'; - -export interface Props { - sloList: SLOWithSummaryResponse[]; - activeAlertsBySlo: ActiveAlerts; - rulesBySlo?: UseFetchRulesForSloResponse['data']; - loading: boolean; - error: boolean; - isCompact?: boolean; -} - -export function SloListItems({ - sloList, - activeAlertsBySlo, - rulesBySlo, - loading, - error, - isCompact = true, -}: Props) { - const { isLoading: historicalSummaryLoading, data: historicalSummaries = [] } = - useFetchHistoricalSummary({ - list: sloList.map((slo) => ({ sloId: slo.id, instanceId: slo.instanceId ?? ALL_VALUE })), - }); - - return ( - - {isCompact && } - {!isCompact && ( - - {sloList.map((slo) => ( - - - historicalSummary.sloId === slo.id && - historicalSummary.instanceId === (slo.instanceId ?? ALL_VALUE) - )?.data - } - historicalSummaryLoading={historicalSummaryLoading} - slo={slo} - /> - - ))} - - )} - - ); -} diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_list_search_bar.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_list_search_bar.tsx index 74c47aec2924f..42ece861e3542 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/slo_list_search_bar.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/slo_list_search_bar.tsx @@ -30,7 +30,7 @@ export type Item = EuiSelectableOption & { export type ViewMode = 'default' | 'compact'; export function SloListSearchBar() { - const { state, store: storeState } = useUrlSearchState(); + const { state, onStateChange: onChange } = useUrlSearchState(); const { kqlQuery, filters } = state; const containerRef = React.useRef(null); @@ -47,7 +47,7 @@ export function SloListSearchBar() { }); const onStateChange = (newState: Partial) => { - storeState({ page: 0, ...newState }); + onChange({ page: 0, ...newState }); }; const { diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_list_view/slo_list_view.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_list_view/slo_list_view.tsx new file mode 100644 index 0000000000000..f353f4743775a --- /dev/null +++ b/x-pack/plugins/observability/public/pages/slos/components/slo_list_view/slo_list_view.tsx @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { ALL_VALUE, SLOWithSummaryResponse } from '@kbn/slo-schema'; +import React from 'react'; +import { useFetchActiveAlerts } from '../../../../hooks/slo/use_fetch_active_alerts'; +import { useFetchHistoricalSummary } from '../../../../hooks/slo/use_fetch_historical_summary'; +import { useFetchRulesForSlo } from '../../../../hooks/slo/use_fetch_rules_for_slo'; +import { SloListEmpty } from '../slo_list_empty'; +import { SloListError } from '../slo_list_error'; +import { SloListItem } from '../slo_list_item'; + +export interface Props { + sloList: SLOWithSummaryResponse[]; + loading: boolean; + error: boolean; +} + +export function SloListView({ sloList, loading, error }: Props) { + const sloIdsAndInstanceIds = sloList.map( + (slo) => [slo.id, slo.instanceId ?? ALL_VALUE] as [string, string] + ); + const { data: activeAlertsBySlo } = useFetchActiveAlerts({ sloIdsAndInstanceIds }); + const { data: rulesBySlo } = useFetchRulesForSlo({ + sloIds: sloIdsAndInstanceIds.map((item) => item[0]), + }); + const { isLoading: historicalSummaryLoading, data: historicalSummaries = [] } = + useFetchHistoricalSummary({ + list: sloList.map((slo) => ({ sloId: slo.id, instanceId: slo.instanceId ?? ALL_VALUE })), + }); + + if (!loading && !error && sloList.length === 0) { + return ; + } + + if (!loading && error) { + return ; + } + + return ( + + {sloList.map((slo) => ( + + + historicalSummary.sloId === slo.id && + historicalSummary.instanceId === (slo.instanceId ?? ALL_VALUE) + )?.data + } + historicalSummaryLoading={historicalSummaryLoading} + slo={slo} + /> + + ))} + + ); +} diff --git a/x-pack/plugins/observability/public/pages/slos/components/slos_view.tsx b/x-pack/plugins/observability/public/pages/slos/components/slos_view.tsx index c2e509d77754a..ca093ccef1043 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/slos_view.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/slos_view.tsx @@ -5,33 +5,24 @@ * 2.0. */ -import { ALL_VALUE, SLOWithSummaryResponse } from '@kbn/slo-schema'; -import React from 'react'; import { EuiFlexItem } from '@elastic/eui'; -import { useFetchRulesForSlo } from '../../../hooks/slo/use_fetch_rules_for_slo'; -import { useFetchActiveAlerts } from '../../../hooks/slo/use_fetch_active_alerts'; +import { SLOWithSummaryResponse } from '@kbn/slo-schema'; +import React from 'react'; import { SloListCardView } from './card_view/slos_card_view'; +import { SloListCompactView } from './compact_view/slo_list_compact_view'; import { SloListEmpty } from './slo_list_empty'; import { SloListError } from './slo_list_error'; -import { SloListItems } from './slo_list_items'; +import { SloListView } from './slo_list_view/slo_list_view'; +import { SLOView } from './toggle_slo_view'; export interface Props { sloList: SLOWithSummaryResponse[]; loading: boolean; error: boolean; - sloView: string; + sloView: SLOView; } export function SlosView({ sloList, loading, error, sloView }: Props) { - const sloIdsAndInstanceIds = sloList.map( - (slo) => [slo.id, slo.instanceId ?? ALL_VALUE] as [string, string] - ); - - const { data: activeAlertsBySlo } = useFetchActiveAlerts({ sloIdsAndInstanceIds }); - const { data: rulesBySlo } = useFetchRulesForSlo({ - sloIds: sloIdsAndInstanceIds.map((item) => item[0]), - }); - if (!loading && !error && sloList.length === 0) { return ; } @@ -41,24 +32,14 @@ export function SlosView({ sloList, loading, error, sloView }: Props) { return sloView === 'cardView' ? ( - + ) : ( - + {sloView === 'compactView' && ( + + )} + {sloView === 'listView' && } ); } diff --git a/x-pack/plugins/observability/public/pages/slos/components/toggle_slo_view.tsx b/x-pack/plugins/observability/public/pages/slos/components/toggle_slo_view.tsx index 40939166209b9..0a29c66944888 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/toggle_slo_view.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/toggle_slo_view.tsx @@ -5,23 +5,23 @@ * 2.0. */ -import React from 'react'; -import { i18n } from '@kbn/i18n'; import { EuiButtonGroup, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { FindSLOResponse } from '@kbn/slo-schema'; -import { SearchState } from '../hooks/use_url_search_state'; -import { SortBySelect } from './common/sort_by_select'; - +import React from 'react'; +import type { SearchState } from '../hooks/use_url_search_state'; +import { SLOSortBy } from './common/sort_by_select'; +import { SloGroupBy } from './slo_list_group_by'; export type SLOView = 'cardView' | 'listView' | 'compactView'; interface Props { onChangeView: (view: SLOView) => void; + onStateChange: (newState: Partial) => void; sloView: SLOView; + state: SearchState; sloList?: FindSLOResponse; loading: boolean; - initialState: SearchState; - onStateChange: (newState: Partial) => void; } const toggleButtonsIcons = [ @@ -46,14 +46,7 @@ const toggleButtonsIcons = [ }, ]; -export function ToggleSLOView({ - sloView, - onChangeView, - sloList, - loading, - initialState, - onStateChange, -}: Props) { +export function ToggleSLOView({ sloView, onChangeView, onStateChange, sloList, state }: Props) { const total = sloList?.total ?? 0; const pageSize = sloList?.perPage ?? 0; const pageIndex = sloList?.page ?? 1; @@ -64,27 +57,32 @@ export function ToggleSLOView({ return ( - - {`${rangeStart}-${rangeEnd}`}, - total, - slos: ( - - - - ), - }} - /> - + {!state.groupBy && ( + + {`${rangeStart}-${rangeEnd}`}, + total, + slos: ( + + + + ), + }} + /> + + )} + + + - + { + const { uiSettings } = useKibana().services; + const percentFormat = uiSettings.get('format:percent:defaultPattern'); + + const formattedSLIValue = + sliValue !== undefined && sliValue !== null ? numeral(sliValue).format(percentFormat) : null; + + return formattedSLIValue; +}; diff --git a/x-pack/plugins/observability/public/pages/slos/hooks/use_url_search_state.ts b/x-pack/plugins/observability/public/pages/slos/hooks/use_url_search_state.ts index 7bd7555a4c126..12154ffd49dd0 100644 --- a/x-pack/plugins/observability/public/pages/slos/hooks/use_url_search_state.ts +++ b/x-pack/plugins/observability/public/pages/slos/hooks/use_url_search_state.ts @@ -12,6 +12,7 @@ import { Filter } from '@kbn/es-query'; import { useEffect, useRef, useState } from 'react'; import { DEFAULT_SLO_PAGE_SIZE } from '../../../../common/slo/constants'; import type { SortField, SortDirection } from '../components/slo_list_search_bar'; +import type { GroupByField } from '../components/slo_list_group_by'; import type { SLOView } from '../components/toggle_slo_view'; export const SLO_LIST_SEARCH_URL_STORAGE_KEY = 'search'; @@ -25,6 +26,7 @@ export interface SearchState { direction: SortDirection; }; view: SLOView; + groupBy: GroupByField; filters: Filter[]; lastRefresh?: number; tagsFilter?: Filter; @@ -37,13 +39,14 @@ export const DEFAULT_STATE = { perPage: DEFAULT_SLO_PAGE_SIZE, sort: { by: 'status' as const, direction: 'desc' as const }, view: 'cardView' as const, + groupBy: 'ungrouped' as const, filters: [], lastRefresh: 0, }; export function useUrlSearchState(): { state: SearchState; - store: (state: Partial) => Promise; + onStateChange: (state: Partial) => Promise; } { const [state, setState] = useState(DEFAULT_STATE); const history = useHistory(); @@ -72,10 +75,9 @@ export function useUrlSearchState(): { sub?.unsubscribe(); }; }, [urlStateStorage]); - return { state: deepmerge(DEFAULT_STATE, state), - store: (newState: Partial) => + onStateChange: (newState: Partial) => urlStateStorage.current?.set( SLO_LIST_SEARCH_URL_STORAGE_KEY, { ...state, ...newState }, diff --git a/x-pack/plugins/observability/public/pages/slos/slos.test.tsx b/x-pack/plugins/observability/public/pages/slos/slos.test.tsx index 6605eab36b5d7..5b7576fc49a18 100644 --- a/x-pack/plugins/observability/public/pages/slos/slos.test.tsx +++ b/x-pack/plugins/observability/public/pages/slos/slos.test.tsx @@ -21,6 +21,7 @@ import { useDeleteSlo } from '../../hooks/slo/use_delete_slo'; import { useFetchHistoricalSummary } from '../../hooks/slo/use_fetch_historical_summary'; import { useFetchSloList } from '../../hooks/slo/use_fetch_slo_list'; import { useLicense } from '../../hooks/use_license'; +import { TagsList } from '@kbn/observability-shared-plugin/public'; import { useKibana } from '../../utils/kibana_react'; import { render } from '../../utils/test_helper'; import { SlosPage } from './slos'; @@ -46,6 +47,8 @@ const useCreateSloMock = useCreateSlo as jest.Mock; const useDeleteSloMock = useDeleteSlo as jest.Mock; const useFetchHistoricalSummaryMock = useFetchHistoricalSummary as jest.Mock; const useCapabilitiesMock = useCapabilities as jest.Mock; +const TagsListMock = TagsList as jest.Mock; +TagsListMock.mockReturnValue(
Tags list
); const mockCreateSlo = jest.fn(); const mockDeleteSlo = jest.fn(); diff --git a/x-pack/plugins/observability/public/pages/slos/slos.tsx b/x-pack/plugins/observability/public/pages/slos/slos.tsx index 2281acc1e5976..259c2d7d2882a 100644 --- a/x-pack/plugins/observability/public/pages/slos/slos.tsx +++ b/x-pack/plugins/observability/public/pages/slos/slos.tsx @@ -31,7 +31,13 @@ export function SlosPage() { const { ObservabilityPageTemplate } = usePluginContext(); const { hasAtLeast } = useLicense(); - const { isLoading, isError, data: sloList } = useFetchSloList(); + const { + isLoading, + isError, + data: sloList, + } = useFetchSloList({ + perPage: 0, + }); const { total } = sloList ?? { total: 0 }; useBreadcrumbs([ diff --git a/x-pack/plugins/observability/public/plugin.ts b/x-pack/plugins/observability/public/plugin.ts index d2dcf8c266976..5d6e825d83c43 100644 --- a/x-pack/plugins/observability/public/plugin.ts +++ b/x-pack/plugins/observability/public/plugin.ts @@ -320,7 +320,11 @@ export class Plugin coreSetup.application.register(app); - registerObservabilityRuleTypes(config, this.observabilityRuleTypeRegistry, logsExplorerLocator); + registerObservabilityRuleTypes( + this.observabilityRuleTypeRegistry, + coreSetup.uiSettings, + logsExplorerLocator + ); const assertPlatinumLicense = async () => { const licensing = await pluginsSetup.licensing; diff --git a/x-pack/plugins/observability/public/rules/register_observability_rule_types.ts b/x-pack/plugins/observability/public/rules/register_observability_rule_types.ts index af92943f1cc55..1915778fc2c4b 100644 --- a/x-pack/plugins/observability/public/rules/register_observability_rule_types.ts +++ b/x-pack/plugins/observability/public/rules/register_observability_rule_types.ts @@ -7,7 +7,7 @@ import { lazy } from 'react'; import { i18n } from '@kbn/i18n'; -import type { SerializedSearchSourceFields } from '@kbn/data-plugin/common'; +import { SerializedSearchSourceFields } from '@kbn/data-plugin/common'; import { ALERT_REASON, ALERT_RULE_PARAMETERS, @@ -16,11 +16,14 @@ import { } from '@kbn/rule-data-utils'; import type { DiscoverAppLocatorParams } from '@kbn/discover-plugin/common'; import type { LocatorPublic } from '@kbn/share-plugin/common'; +import { IUiSettingsClient } from '@kbn/core-ui-settings-browser'; import type { MetricExpression } from '../components/custom_threshold/types'; -import type { CustomThresholdExpressionMetric } from '../../common/custom_threshold_rule/types'; +import type { + CustomMetricExpressionParams, + CustomThresholdExpressionMetric, +} from '../../common/custom_threshold_rule/types'; import { getViewInAppUrl } from '../../common/custom_threshold_rule/get_view_in_app_url'; import { SLO_ID_FIELD, SLO_INSTANCE_ID_FIELD } from '../../common/field_names/slo'; -import { ConfigSchema } from '../plugin'; import { ObservabilityRuleTypeRegistry } from './create_observability_rule_type_registry'; import { SLO_BURN_RATE_RULE_TYPE_ID } from '../../common/constants'; import { validateBurnRateRule } from '../components/burn_rate_rule_editor/validation'; @@ -86,8 +89,8 @@ const getDataViewId = (searchConfiguration?: SerializedSearchSourceFields) => : searchConfiguration?.index?.title; export const registerObservabilityRuleTypes = async ( - config: ConfigSchema, observabilityRuleTypeRegistry: ObservabilityRuleTypeRegistry, + uiSettings: IUiSettingsClient, logsExplorerLocator?: LocatorPublic ) => { observabilityRuleTypeRegistry.register({ @@ -117,7 +120,13 @@ export const registerObservabilityRuleTypes = async ( ), priority: 100, }); - + const validateCustomThresholdWithUiSettings = ({ + criteria, + searchConfiguration, + }: { + criteria: CustomMetricExpressionParams[]; + searchConfiguration: SerializedSearchSourceFields; + }) => validateCustomThreshold({ criteria, searchConfiguration, uiSettings }); observabilityRuleTypeRegistry.register({ id: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, description: i18n.translate( @@ -133,7 +142,7 @@ export const registerObservabilityRuleTypes = async ( ruleParamsExpression: lazy( () => import('../components/custom_threshold/custom_threshold_rule_expression') ), - validate: validateCustomThreshold, + validate: validateCustomThresholdWithUiSettings, defaultActionMessage: thresholdDefaultActionMessage, defaultRecoveryMessage: thresholdDefaultRecoveryMessage, requiresAppContext: false, diff --git a/x-pack/plugins/observability/server/domain/models/common.ts b/x-pack/plugins/observability/server/domain/models/common.ts index 3e734146e8994..6e82a9e7cbbe9 100644 --- a/x-pack/plugins/observability/server/domain/models/common.ts +++ b/x-pack/plugins/observability/server/domain/models/common.ts @@ -11,11 +11,13 @@ import { historicalSummarySchema, statusSchema, summarySchema, + groupSummarySchema, } from '@kbn/slo-schema'; type Status = t.TypeOf; type DateRange = t.TypeOf; type HistoricalSummary = t.TypeOf; type Summary = t.TypeOf; +type GroupSummary = t.TypeOf; -export type { DateRange, HistoricalSummary, Status, Summary }; +export type { DateRange, HistoricalSummary, Status, Summary, GroupSummary }; diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.test.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.test.ts index dfc6daa82d40d..9e3eab1e8a054 100644 --- a/x-pack/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.test.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.test.ts @@ -1056,7 +1056,7 @@ describe('The custom threshold alert type', () => { const { action } = mostRecentAction(instanceID); const reasons = action.reason; expect(reasons).toBe( - 'Average test.metric.1 is 1, above the threshold of 1; Average test.metric.2 is 3, above the threshold of 3. (duration: 1 min, data view: mockedDataViewName)' + 'Average test.metric.1 is 1, above or equal the threshold of 1; Average test.metric.2 is 3, above or equal the threshold of 3. (duration: 1 min, data view: mockedDataViewName)' ); }); }); diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/check_missing_group.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/check_missing_group.ts index 6b8041b448484..c45120cc62fa3 100644 --- a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/check_missing_group.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/check_missing_group.ts @@ -10,7 +10,7 @@ import type { Logger } from '@kbn/logging'; import { isString, get, identity } from 'lodash'; import { CustomMetricExpressionParams } from '../../../../../common/custom_threshold_rule/types'; import type { BucketKey } from './get_data'; -import { calculateCurrentTimeframe, createBaseFilters } from './metric_query'; +import { calculateCurrentTimeFrame, createBaseFilters } from './metric_query'; export interface MissingGroupsRecord { key: string; @@ -31,8 +31,8 @@ export const checkMissingGroups = async ( if (missingGroups.length === 0) { return missingGroups; } - const currentTimeframe = calculateCurrentTimeframe(metricParams, timeframe); - const baseFilters = createBaseFilters(metricParams, currentTimeframe, timeFieldName, filterQuery); + const currentTimeFrame = calculateCurrentTimeFrame(metricParams, timeframe); + const baseFilters = createBaseFilters(currentTimeFrame, timeFieldName, filterQuery); const groupByFields = isString(groupBy) ? [groupBy] : groupBy ? groupBy : []; const searches = missingGroups.flatMap((group) => { diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_custom_metrics_aggregations.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_custom_metrics_aggregations.ts index 255475c8d8a5c..3ab20c5a632c7 100644 --- a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_custom_metrics_aggregations.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_custom_metrics_aggregations.ts @@ -7,11 +7,17 @@ import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query'; import { isEmpty } from 'lodash'; -import { CustomThresholdExpressionMetric } from '../../../../../common/custom_threshold_rule/types'; +import { + Aggregators, + CustomThresholdExpressionMetric, +} from '../../../../../common/custom_threshold_rule/types'; +import { createRateAggsBuckets, createRateAggsBucketScript } from './create_rate_aggregation'; export const createCustomMetricsAggregations = ( id: string, customMetrics: CustomThresholdExpressionMetric[], + currentTimeFrame: { start: number; end: number }, + timeFieldName: string, equation?: string ) => { const bucketsPath: { [id: string]: string } = {}; @@ -30,6 +36,28 @@ export const createCustomMetricsAggregations = ( }, }; } + if (aggregation === Aggregators.P95 || aggregation === Aggregators.P99) { + bucketsPath[metric.name] = key; + return { + ...acc, + [key]: { + percentiles: { + field: metric.field, + percents: [aggregation === Aggregators.P95 ? 95 : 99], + keyed: true, + }, + }, + }; + } + + if (aggregation === Aggregators.RATE) { + bucketsPath[metric.name] = key; + return { + ...acc, + ...createRateAggsBuckets(currentTimeFrame, key, timeFieldName, metric.field || ''), + ...createRateAggsBucketScript(currentTimeFrame, key), + }; + } if (aggregation && metric.field) { bucketsPath[metric.name] = key; diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_rate_aggregation.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_rate_aggregation.ts new file mode 100644 index 0000000000000..b68101d8e9b35 --- /dev/null +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_rate_aggregation.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 moment from 'moment'; +import { calculateRateTimeranges } from '../utils'; +export const createRateAggsBucketScript = ( + timeframe: { start: number; end: number }, + id: string +) => { + const { intervalInSeconds } = calculateRateTimeranges({ + to: timeframe.end, + from: timeframe.start, + }); + return { + [id]: { + bucket_script: { + buckets_path: { + first: `${id}_first_bucket.maxValue`, + second: `${id}_second_bucket.maxValue`, + }, + script: `params.second > 0.0 && params.first > 0.0 && params.second > params.first ? (params.second - params.first) / ${intervalInSeconds}: 0`, + }, + }, + }; +}; + +export const createRateAggsBuckets = ( + timeframe: { start: number; end: number }, + id: string, + timeFieldName: string, + field: string +) => { + const { firstBucketRange, secondBucketRange } = calculateRateTimeranges({ + to: timeframe.end, + from: timeframe.start, + }); + + return { + [`${id}_first_bucket`]: { + filter: { + range: { + [timeFieldName]: { + gte: moment(firstBucketRange.from).toISOString(), + lt: moment(firstBucketRange.to).toISOString(), + }, + }, + }, + aggs: { maxValue: { max: { field } } }, + }, + [`${id}_second_bucket`]: { + filter: { + range: { + [timeFieldName]: { + gte: moment(secondBucketRange.from).toISOString(), + lt: moment(secondBucketRange.to).toISOString(), + }, + }, + }, + aggs: { maxValue: { max: { field } } }, + }, + }; +}; diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_timerange.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_timerange.ts index a6c39adcd3204..0d82b5df637b8 100644 --- a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_timerange.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/create_timerange.ts @@ -10,11 +10,15 @@ import moment from 'moment'; export const createTimerange = ( interval: number, timeframe: { end: string; start: string }, - lastPeriodEnd?: number + lastPeriodEnd?: number, + isRateAgg?: boolean ) => { const end = moment(timeframe.end).valueOf(); let start = moment(timeframe.start).valueOf(); + const minimumBuckets = isRateAgg ? 2 : 1; + + interval = interval * minimumBuckets; start = start - interval; // Use lastPeriodEnd - interval when it's less than start diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/evaluate_rule.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/evaluate_rule.ts index f5b3f0adf1bdd..59f5801613dd0 100644 --- a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/evaluate_rule.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/evaluate_rule.ts @@ -8,8 +8,11 @@ import moment from 'moment'; import { ElasticsearchClient } from '@kbn/core/server'; import type { Logger } from '@kbn/logging'; -import { CustomMetricExpressionParams } from '../../../../../common/custom_threshold_rule/types'; import { getIntervalInSeconds } from '../../../../../common/utils/get_interval_in_seconds'; +import { + Aggregators, + CustomMetricExpressionParams, +} from '../../../../../common/custom_threshold_rule/types'; import { AdditionalContext } from '../utils'; import { SearchConfigurationType } from '../types'; import { createTimerange } from './create_timerange'; @@ -50,7 +53,15 @@ export const evaluateRule = async metric.aggType === Aggregators.RATE + ); + const calculatedTimerange = createTimerange( + intervalAsMS, + timeframe, + lastPeriodEnd, + isRateAggregation + ); const currentValues = await getData( esClient, diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/format_alert_result.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/format_alert_result.ts index c0220d89c9d98..02acac53023d0 100644 --- a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/format_alert_result.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/format_alert_result.ts @@ -6,8 +6,9 @@ */ import { i18n } from '@kbn/i18n'; +import { EventsAsUnit } from '../../../../../common/constants'; +import { metricValueFormatter } from '../../../../../common/custom_threshold_rule/metric_value_formatter'; import { Aggregators } from '../../../../../common/custom_threshold_rule/types'; -import { createFormatter } from '../../../../../common/custom_threshold_rule/formatters'; import { AVERAGE_I18N, CARDINALITY_I18N, @@ -15,6 +16,9 @@ import { DOCUMENT_COUNT_I18N, MAX_I18N, MIN_I18N, + PERCENTILE_95_I18N, + PERCENTILE_99_I18N, + RATE_I18N, SUM_I18N, } from '../translations'; import { Evaluation } from './evaluate_rule'; @@ -31,6 +35,12 @@ export const getLabel = (criterion: Evaluation) => { return DOCUMENT_COUNT_I18N; case Aggregators.AVERAGE: return AVERAGE_I18N(criterion.metrics[0].field!); + case Aggregators.P95: + return PERCENTILE_95_I18N(criterion.metrics[0].field!); + case Aggregators.P99: + return PERCENTILE_99_I18N(criterion.metrics[0].field!); + case Aggregators.RATE: + return RATE_I18N(criterion.metrics[0].field!); case Aggregators.MAX: return MAX_I18N(criterion.metrics[0].field!); case Aggregators.MIN: @@ -51,21 +61,27 @@ export const formatAlertResult = (evaluationResult: Evaluation): FormattedEvalua { defaultMessage: '[NO DATA]' } ); - let formatter = createFormatter('highPrecision'); const label = getLabel(evaluationResult); - if (metrics.length === 1 && metrics[0].field && metrics[0].field.endsWith('.pct')) { - formatter = createFormatter('percent'); - } + const perSecIfRate = metrics[0].aggType === Aggregators.RATE ? '/s' : ''; + const eventsAsUnit = + metrics[0].aggType === Aggregators.RATE && + !metrics[0].field?.endsWith('.pct') && + !metrics[0].field?.endsWith('.bytes') + ? ` ${EventsAsUnit}` + : ''; + const rateUnitPerSec = eventsAsUnit + perSecIfRate; return { ...evaluationResult, currentValue: - currentValue !== null && currentValue !== undefined ? formatter(currentValue) : noDataValue, + currentValue !== null && currentValue !== undefined + ? metricValueFormatter(currentValue, metrics[0].field) + rateUnitPerSec + : noDataValue, label: label || CUSTOM_EQUATION_I18N, threshold: Array.isArray(threshold) - ? threshold.map((v: number) => formatter(v)) - : [formatter(threshold)], + ? threshold.map((v: number) => metricValueFormatter(v, metrics[0].field) + rateUnitPerSec) + : [metricValueFormatter(currentValue, metrics[0].field) + rateUnitPerSec], comparator, }; }; diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/metric_query.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/metric_query.ts index 6471926522929..3cc1eee92fec9 100644 --- a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/metric_query.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/metric_query.ts @@ -6,7 +6,10 @@ */ import moment from 'moment'; -import { CustomMetricExpressionParams } from '../../../../../common/custom_threshold_rule/types'; +import { + Aggregators, + CustomMetricExpressionParams, +} from '../../../../../common/custom_threshold_rule/types'; import { createCustomMetricsAggregations } from './create_custom_metrics_aggregations'; import { CONTAINER_ID, @@ -19,16 +22,23 @@ import { createBucketSelector } from './create_bucket_selector'; import { wrapInCurrentPeriod } from './wrap_in_period'; import { getParsedFilterQuery } from '../../../../utils/get_parsed_filtered_query'; -export const calculateCurrentTimeframe = ( +export const calculateCurrentTimeFrame = ( metricParams: CustomMetricExpressionParams, timeframe: { start: number; end: number } -) => ({ - ...timeframe, - start: moment(timeframe.end).subtract(metricParams.timeSize, metricParams.timeUnit).valueOf(), -}); +) => { + const isRateAgg = metricParams.metrics.some((metric) => metric.aggType === Aggregators.RATE); + return { + ...timeframe, + start: moment(timeframe.end) + .subtract( + isRateAgg ? metricParams.timeSize * 2 : metricParams.timeSize, + metricParams.timeUnit + ) + .valueOf(), + }; +}; export const createBaseFilters = ( - metricParams: CustomMetricExpressionParams, timeframe: { start: number; end: number }, timeFieldName: string, filterQuery?: string @@ -63,14 +73,16 @@ export const getElasticsearchMetricQuery = ( ) => { // We need to make a timeframe that represents the current timeframe as opposed // to the total timeframe (which includes the last period). - const currentTimeframe = { - ...calculateCurrentTimeframe(metricParams, timeframe), + const currentTimeFrame = { + ...calculateCurrentTimeFrame(metricParams, timeframe), timeFieldName, }; const metricAggregations = createCustomMetricsAggregations( 'aggregatedValue', metricParams.metrics, + currentTimeFrame, + timeFieldName, metricParams.equation ); @@ -82,7 +94,7 @@ export const getElasticsearchMetricQuery = ( lastPeriodEnd ); - const currentPeriod = wrapInCurrentPeriod(currentTimeframe, metricAggregations); + const currentPeriod = wrapInCurrentPeriod(currentTimeFrame, metricAggregations); const containerIncludesList = ['container.*']; const containerExcludesList = [ @@ -184,7 +196,7 @@ export const getElasticsearchMetricQuery = ( aggs.groupings.composite.after = afterKey; } - const baseFilters = createBaseFilters(metricParams, timeframe, timeFieldName, filterQuery); + const baseFilters = createBaseFilters(timeframe, timeFieldName, filterQuery); return { track_total_hits: true, diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/messages.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/messages.ts index 973ce8f57093a..ca160d06b6573 100644 --- a/x-pack/plugins/observability/server/lib/rules/custom_threshold/messages.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/messages.ts @@ -16,6 +16,8 @@ import { BETWEEN_TEXT, NOT_BETWEEN_TEXT, CUSTOM_EQUATION_I18N, + ABOVE_OR_EQ_TEXT, + BELOW_OR_EQ_TEXT, } from './translations'; import { UNGROUPED_FACTORY_KEY } from './constants'; @@ -33,11 +35,13 @@ const recoveredComparatorToI18n = ( case Comparator.OUTSIDE_RANGE: return BETWEEN_TEXT; case Comparator.GT: + return ABOVE_TEXT; case Comparator.GT_OR_EQ: - return BELOW_TEXT; + return ABOVE_OR_EQ_TEXT; case Comparator.LT: + return BELOW_TEXT; case Comparator.LT_OR_EQ: - return ABOVE_TEXT; + return BELOW_OR_EQ_TEXT; } }; @@ -48,11 +52,13 @@ const alertComparatorToI18n = (comparator: Comparator) => { case Comparator.OUTSIDE_RANGE: return NOT_BETWEEN_TEXT; case Comparator.GT: - case Comparator.GT_OR_EQ: return ABOVE_TEXT; + case Comparator.GT_OR_EQ: + return ABOVE_OR_EQ_TEXT; case Comparator.LT: - case Comparator.LT_OR_EQ: return BELOW_TEXT; + case Comparator.LT_OR_EQ: + return BELOW_OR_EQ_TEXT; } }; diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/register_custom_threshold_rule_type.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/register_custom_threshold_rule_type.ts index 56dc9fd721e02..5e9c2e0cea019 100644 --- a/x-pack/plugins/observability/server/lib/rules/custom_threshold/register_custom_threshold_rule_type.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/register_custom_threshold_rule_type.ts @@ -18,7 +18,7 @@ import { createLifecycleExecutor, IRuleDataClient } from '@kbn/rule-registry-plu import { LicenseType } from '@kbn/licensing-plugin/server'; import { EsQueryRuleParamsExtractedParams } from '@kbn/stack-alerts-plugin/server/rule_types/es_query/rule_type_params'; import { observabilityFeatureId, observabilityPaths } from '../../../../common'; -import { Comparator } from '../../../../common/custom_threshold_rule/types'; +import { Aggregators, Comparator } from '../../../../common/custom_threshold_rule/types'; import { THRESHOLD_RULE_REGISTRATION_CONTEXT } from '../../../common/constants'; import { @@ -76,6 +76,8 @@ export function thresholdRuleType( timeUnit: schema.string(), timeSize: schema.number(), }; + const allowedAggregators = Object.values(Aggregators); + allowedAggregators.splice(Object.values(Aggregators).indexOf(Aggregators.COUNT), 1); const customCriterion = schema.object({ ...baseCriterion, @@ -85,7 +87,7 @@ export function thresholdRuleType( schema.oneOf([ schema.object({ name: schema.string(), - aggType: oneOfLiterals(['avg', 'sum', 'max', 'min', 'cardinality']), + aggType: oneOfLiterals(allowedAggregators), field: schema.string(), filter: schema.never(), }), @@ -105,6 +107,17 @@ export function thresholdRuleType( label: schema.maybe(schema.string()), }); + const paramsSchema = schema.object( + { + criteria: schema.arrayOf(customCriterion), + groupBy: schema.maybe(schema.oneOf([schema.string(), schema.arrayOf(schema.string())])), + alertOnNoData: schema.maybe(schema.boolean()), + alertOnGroupDisappear: schema.maybe(schema.boolean()), + searchConfiguration: searchConfigurationSchema, + }, + { unknowns: 'allow' } + ); + return { id: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, name: i18n.translate('xpack.observability.threshold.ruleName', { @@ -112,16 +125,13 @@ export function thresholdRuleType( }), fieldsForAAD: CUSTOM_THRESHOLD_AAD_FIELDS, validate: { - params: schema.object( - { - criteria: schema.arrayOf(customCriterion), - groupBy: schema.maybe(schema.oneOf([schema.string(), schema.arrayOf(schema.string())])), - alertOnNoData: schema.maybe(schema.boolean()), - alertOnGroupDisappear: schema.maybe(schema.boolean()), - searchConfiguration: searchConfigurationSchema, - }, - { unknowns: 'allow' } - ), + params: paramsSchema, + }, + schemas: { + params: { + type: 'config-schema' as const, + schema: paramsSchema, + }, }, defaultActionGroupId: FIRED_ACTION.id, actionGroups: [FIRED_ACTION, NO_DATA_ACTION], diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/translations.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/translations.ts index 301e793150dc6..020229c27a4ad 100644 --- a/x-pack/plugins/observability/server/lib/rules/custom_threshold/translations.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/translations.ts @@ -22,6 +22,30 @@ export const AVERAGE_I18N = (metric: string) => }, }); +export const PERCENTILE_99_I18N = (metric: string) => + i18n.translate('xpack.observability.customThreshold.rule.aggregators.p99', { + defaultMessage: '99th percentile of {metric}', + values: { + metric, + }, + }); + +export const PERCENTILE_95_I18N = (metric: string) => + i18n.translate('xpack.observability.customThreshold.rule.aggregators.p95', { + defaultMessage: '95th percentile of {metric}', + values: { + metric, + }, + }); + +export const RATE_I18N = (metric: string) => + i18n.translate('xpack.observability.customThreshold.rule.aggregators.rate', { + defaultMessage: 'Rate of {metric}', + values: { + metric, + }, + }); + export const MAX_I18N = (metric: string) => i18n.translate('xpack.observability.customThreshold.rule.aggregators.max', { defaultMessage: 'Max {metric}', @@ -70,6 +94,13 @@ export const BELOW_TEXT = i18n.translate( } ); +export const BELOW_OR_EQ_TEXT = i18n.translate( + 'xpack.observability.customThreshold.rule.threshold.belowOrEqual', + { + defaultMessage: 'below or equal', + } +); + export const ABOVE_TEXT = i18n.translate( 'xpack.observability.customThreshold.rule.threshold.above', { @@ -77,6 +108,13 @@ export const ABOVE_TEXT = i18n.translate( } ); +export const ABOVE_OR_EQ_TEXT = i18n.translate( + 'xpack.observability.customThreshold.rule.threshold.aboveOrEqual', + { + defaultMessage: 'above or equal', + } +); + export const BETWEEN_TEXT = i18n.translate( 'xpack.observability.customThreshold.rule.threshold.between', { diff --git a/x-pack/plugins/observability/server/lib/rules/slo_burn_rate/register.ts b/x-pack/plugins/observability/server/lib/rules/slo_burn_rate/register.ts index 40873338d5e9f..f70dc3721cb20 100644 --- a/x-pack/plugins/observability/server/lib/rules/slo_burn_rate/register.ts +++ b/x-pack/plugins/observability/server/lib/rules/slo_burn_rate/register.ts @@ -50,6 +50,10 @@ export function sloBurnRateRuleType( basePath: IBasePath, alertsLocator?: LocatorPublic ) { + const paramsSchema = schema.object({ + sloId: schema.string(), + windows: schema.arrayOf(windowSchema), + }); return { id: SLO_BURN_RATE_RULE_TYPE_ID, name: i18n.translate('xpack.observability.slo.rules.burnRate.name', { @@ -57,10 +61,13 @@ export function sloBurnRateRuleType( }), fieldsForAAD: SLO_BURN_RATE_AAD_FIELDS, validate: { - params: schema.object({ - sloId: schema.string(), - windows: schema.arrayOf(windowSchema), - }), + params: paramsSchema, + }, + schemas: { + params: { + type: 'config-schema' as const, + schema: paramsSchema, + }, }, defaultActionGroupId: ALERT_ACTION.id, actionGroups: [ALERT_ACTION, HIGH_PRIORITY_ACTION, MEDIUM_PRIORITY_ACTION, LOW_PRIORITY_ACTION], diff --git a/x-pack/plugins/observability/server/routes/slo/route.ts b/x-pack/plugins/observability/server/routes/slo/route.ts index 436548eb4fbcd..2ec1f37e7ac27 100644 --- a/x-pack/plugins/observability/server/routes/slo/route.ts +++ b/x-pack/plugins/observability/server/routes/slo/route.ts @@ -14,6 +14,7 @@ import { fetchHistoricalSummaryParamsSchema, findSloDefinitionsParamsSchema, findSLOParamsSchema, + findSLOGroupsParamsSchema, getPreviewDataParamsSchema, getSLOBurnRatesParamsSchema, getSLOInstancesParamsSchema, @@ -34,6 +35,7 @@ import { GetSLO, KibanaSavedObjectsSLORepository, UpdateSLO, + FindSLOGroups, } from '../../services/slo'; import { FetchHistoricalSummary } from '../../services/slo/fetch_historical_summary'; import { FindSLODefinitions } from '../../services/slo/find_slo_definitions'; @@ -359,7 +361,6 @@ const findSLORoute = createObservabilityServerRoute({ const spaceId = (await dependencies.spaces?.spacesService?.getActiveSpace(request))?.id ?? 'default'; - const soClient = (await context.core).savedObjects.client; const esClient = (await context.core).elasticsearch.client.asCurrentUser; const repository = new KibanaSavedObjectsSLORepository(soClient, logger); @@ -372,6 +373,25 @@ const findSLORoute = createObservabilityServerRoute({ }, }); +const findSLOGroupsRoute = createObservabilityServerRoute({ + endpoint: 'GET /internal/api/observability/slos/_groups', + options: { + tags: ['access:slo_read'], + access: 'internal', + }, + params: findSLOGroupsParamsSchema, + handler: async ({ context, request, params, logger, dependencies }) => { + await assertPlatinumLicense(context); + const spaceId = + (await dependencies.spaces?.spacesService.getActiveSpace(request))?.id ?? 'default'; + const coreContext = context.core; + const esClient = (await coreContext).elasticsearch.client.asCurrentUser; + const findSLOGroups = new FindSLOGroups(esClient, logger, spaceId); + const response = await findSLOGroups.execute(params?.query ?? {}); + return response; + }, +}); + const deleteSloInstancesRoute = createObservabilityServerRoute({ endpoint: 'POST /api/observability/slos/_delete_instances 2023-10-31', options: { @@ -533,4 +553,5 @@ export const sloRouteRepository = { ...getPreviewData, ...getSLOInstancesRoute, ...resetSLORoute, + ...findSLOGroupsRoute, }; diff --git a/x-pack/plugins/observability/server/services/slo/find_slo.ts b/x-pack/plugins/observability/server/services/slo/find_slo.ts index 690d61a30c260..b92e53bd5cd5b 100644 --- a/x-pack/plugins/observability/server/services/slo/find_slo.ts +++ b/x-pack/plugins/observability/server/services/slo/find_slo.ts @@ -61,7 +61,7 @@ function toPagination(params: FindSLOParams): Pagination { return { page: !isNaN(page) && page >= 1 ? page : DEFAULT_PAGE, - perPage: !isNaN(perPage) && perPage >= 1 ? perPage : DEFAULT_PER_PAGE, + perPage: !isNaN(perPage) && perPage >= 0 ? perPage : DEFAULT_PER_PAGE, }; } diff --git a/x-pack/plugins/observability/server/services/slo/find_slo_groups.ts b/x-pack/plugins/observability/server/services/slo/find_slo_groups.ts new file mode 100644 index 0000000000000..28fd734bb8dd9 --- /dev/null +++ b/x-pack/plugins/observability/server/services/slo/find_slo_groups.ts @@ -0,0 +1,174 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { FindSLOGroupsParams, FindSLOGroupsResponse, Pagination } from '@kbn/slo-schema'; +import { ElasticsearchClient } from '@kbn/core/server'; +import { findSLOGroupsResponseSchema } from '@kbn/slo-schema'; +import { Logger } from '@kbn/core/server'; +import { typedSearch } from '../../utils/queries'; +import { IllegalArgumentError } from '../../errors'; +import { + SLO_SUMMARY_DESTINATION_INDEX_PATTERN, + DEFAULT_SLO_GROUPS_PAGE_SIZE, +} from '../../../common/slo/constants'; +import { Status } from '../../domain/models'; +import { getElastichsearchQueryOrThrow } from './transform_generators'; + +const DEFAULT_PAGE = 1; +const MAX_PER_PAGE = 5000; + +function toPagination(params: FindSLOGroupsParams): Pagination { + const page = Number(params.page); + const perPage = Number(params.perPage); + + if (!isNaN(perPage) && perPage > MAX_PER_PAGE) { + throw new IllegalArgumentError(`perPage limit set to ${MAX_PER_PAGE}`); + } + + return { + page: !isNaN(page) && page >= 1 ? page : DEFAULT_PAGE, + perPage: !isNaN(perPage) && perPage >= 1 ? perPage : DEFAULT_SLO_GROUPS_PAGE_SIZE, + }; +} + +interface SliDocument { + sliValue: number; + status: Status; + 'slo.id': string; +} + +export class FindSLOGroups { + constructor( + private esClient: ElasticsearchClient, + private logger: Logger, + private spaceId: string + ) {} + public async execute(params: FindSLOGroupsParams): Promise { + const pagination = toPagination(params); + const groupBy = params.groupBy; + const kqlQuery = params.kqlQuery ?? ''; + const filters = params.filters ?? ''; + let parsedFilters: any = {}; + + try { + parsedFilters = JSON.parse(filters); + } catch (e) { + this.logger.error(`Failed to parse filters: ${e.message}`); + } + + const response = await typedSearch(this.esClient, { + index: SLO_SUMMARY_DESTINATION_INDEX_PATTERN, + size: 0, + query: { + bool: { + filter: [ + { term: { spaceId: this.spaceId } }, + getElastichsearchQueryOrThrow(kqlQuery), + ...(parsedFilters.filter ?? []), + ], + }, + }, + body: { + aggs: { + groupBy: { + terms: { + field: groupBy, + size: 10000, + }, + aggs: { + worst: { + top_hits: { + sort: { + errorBudgetRemaining: { + order: 'asc', + }, + }, + _source: { + includes: ['sliValue', 'status', 'slo.id', 'slo.instanceId', 'slo.name'], + }, + size: 1, + }, + }, + violated: { + filter: { + term: { + status: 'VIOLATED', + }, + }, + }, + healthy: { + filter: { + term: { + status: 'HEALTHY', + }, + }, + }, + degrading: { + filter: { + term: { + status: 'DEGRADING', + }, + }, + }, + noData: { + filter: { + term: { + status: 'NO_DATA', + }, + }, + }, + bucket_sort: { + bucket_sort: { + sort: [ + { + _key: { + order: 'asc', + }, + }, + ], + from: (pagination.page - 1) * pagination.perPage, + size: pagination.perPage, + }, + }, + }, + }, + distinct_items: { + cardinality: { + field: groupBy, + }, + }, + }, + }, + }); + + const total = response.aggregations?.distinct_items?.value ?? 0; + const results = + response.aggregations?.groupBy?.buckets.reduce((acc, bucket) => { + const sliDocument = bucket.worst?.hits?.hits[0]?._source as SliDocument; + return [ + ...acc, + { + group: bucket.key, + groupBy, + summary: { + total: bucket.doc_count ?? 0, + worst: sliDocument, + violated: bucket.violated?.doc_count, + healthy: bucket.healthy?.doc_count, + degrading: bucket.degrading?.doc_count, + noData: bucket.noData?.doc_count, + }, + }, + ]; + }, [] as Array>) ?? []; + return findSLOGroupsResponseSchema.encode({ + page: pagination.page, + perPage: pagination.perPage, + total, + results, + }); + } +} diff --git a/x-pack/plugins/observability/server/services/slo/index.ts b/x-pack/plugins/observability/server/services/slo/index.ts index 2a939c56fde4b..c9c1ebfc7991c 100644 --- a/x-pack/plugins/observability/server/services/slo/index.ts +++ b/x-pack/plugins/observability/server/services/slo/index.ts @@ -21,3 +21,4 @@ export * from './summay_transform_manager'; export * from './update_slo'; export * from './summary_client'; export * from './get_slo_instances'; +export * from './find_slo_groups'; diff --git a/x-pack/plugins/observability/server/ui_settings.ts b/x-pack/plugins/observability/server/ui_settings.ts index 0ccd75c9ae3a3..0fdf57c97f956 100644 --- a/x-pack/plugins/observability/server/ui_settings.ts +++ b/x-pack/plugins/observability/server/ui_settings.ts @@ -38,6 +38,7 @@ import { profilingAWSCostDiscountRate, profilingCostPervCPUPerHour, enableInfrastructureProfilingIntegration, + enableInfrastructureHostsCustomDashboards, } from '../common/ui_settings_keys'; const betaLabel = i18n.translate('xpack.observability.uiSettings.betaLabel', { @@ -251,6 +252,24 @@ export const uiSettings: Record = { ), schema: schema.boolean(), }, + [enableInfrastructureHostsCustomDashboards]: { + category: [observabilityFeatureId], + name: i18n.translate('xpack.observability.enableInfrastructureHostsCustomDashboards', { + defaultMessage: 'Custom dashboards for Host Details in Infrastructure', + }), + value: false, + description: i18n.translate( + 'xpack.observability.enableInfrastructureHostsCustomDashboardsDescription', + { + defaultMessage: + '{betaLabel} Enable option to link custom dashboards in the Host Details view.', + values: { + betaLabel: `[${betaLabel}]`, + }, + } + ), + schema: schema.boolean(), + }, [enableAwsLambdaMetrics]: { category: [observabilityFeatureId], name: i18n.translate('xpack.observability.enableAwsLambdaMetrics', { diff --git a/x-pack/plugins/observability/tsconfig.json b/x-pack/plugins/observability/tsconfig.json index 8f60c6472d706..7030b487be7e0 100644 --- a/x-pack/plugins/observability/tsconfig.json +++ b/x-pack/plugins/observability/tsconfig.json @@ -107,7 +107,8 @@ "@kbn/field-formats-plugin", "@kbn/aiops-utils", "@kbn/event-annotation-common", - "@kbn/controls-plugin" + "@kbn/controls-plugin", + "@kbn/core-lifecycle-browser" ], "exclude": [ "target/**/*" diff --git a/x-pack/plugins/observability_ai_assistant/public/components/insight/actions_menu.tsx b/x-pack/plugins/observability_ai_assistant/public/components/insight/actions_menu.tsx new file mode 100644 index 0000000000000..2cddc557ee1b6 --- /dev/null +++ b/x-pack/plugins/observability_ai_assistant/public/components/insight/actions_menu.tsx @@ -0,0 +1,96 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React, { useState } from 'react'; +import { EuiButtonIcon, EuiContextMenu, EuiPanel, EuiPopover } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { UseGenAIConnectorsResult } from '../../hooks/use_genai_connectors'; +import { ConnectorSelectorBase } from '../connector_selector/connector_selector_base'; + +export function ActionsMenu({ + connectors, + onEditPrompt, +}: { + connectors: UseGenAIConnectorsResult; + onEditPrompt: () => void; +}) { + const [isPopoverOpen, setPopover] = useState(false); + + const onButtonClick = () => { + setPopover(!isPopoverOpen); + }; + + const closePopover = () => { + setPopover(false); + }; + + const panels = [ + { + id: 0, + title: 'Actions', + items: [ + { + name: ( +
+ {i18n.translate('xpack.observabilityAiAssistant.insight.actions.connector', { + defaultMessage: 'Connector', + })}{' '} + + {connectors.connectors?.find(({ id }) => id === connectors.selectedConnector)?.name} + +
+ ), + icon: 'wrench', + panel: 1, + }, + { + name: i18n.translate('xpack.observabilityAiAssistant.insight.actions.editPrompt', { + defaultMessage: 'Edit prompt', + }), + icon: 'documentEdit', + onClick: () => { + onEditPrompt(); + closePopover(); + }, + }, + ], + }, + { + id: 1, + title: i18n.translate('xpack.observabilityAiAssistant.insight.actions.connector', { + defaultMessage: 'Connector', + }), + content: ( + + + + ), + }, + ]; + + const button = ( + + ); + + return ( + + + + ); +} diff --git a/x-pack/plugins/observability_ai_assistant/public/components/insight/insight.tsx b/x-pack/plugins/observability_ai_assistant/public/components/insight/insight.tsx index bfe01db7fb49f..2d57ff92cd5bc 100644 --- a/x-pack/plugins/observability_ai_assistant/public/components/insight/insight.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/components/insight/insight.tsx @@ -4,8 +4,18 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { last } from 'lodash'; +import { + EuiHorizontalRule, + EuiButtonEmpty, + EuiButtonIcon, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiText, + EuiTextArea, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { cloneDeep, last } from 'lodash'; import React, { useEffect, useRef, useState } from 'react'; import { MessageRole, type Message } from '../../../common/types'; import { sendEvent, TELEMETRY } from '../../analytics'; @@ -21,12 +31,16 @@ import { RegenerateResponseButton } from '../buttons/regenerate_response_button' import { StartChatButton } from '../buttons/start_chat_button'; import { StopGeneratingButton } from '../buttons/stop_generating_button'; import { ChatFlyout } from '../chat/chat_flyout'; -import { ConnectorSelectorBase } from '../connector_selector/connector_selector_base'; import { FeedbackButtons } from '../feedback_buttons'; import { MessagePanel } from '../message_panel/message_panel'; import { MessageText } from '../message_panel/message_text'; import { MissingCredentialsCallout } from '../missing_credentials_callout'; import { InsightBase } from './insight_base'; +import { ActionsMenu } from './actions_menu'; + +function getLastMessageOfType(messages: Message[], role: MessageRole) { + return last(messages.filter((msg) => msg.message.role === role)); +} function ChatContent({ title: defaultTitle, @@ -48,9 +62,7 @@ function ChatContent({ persist: false, }); - const lastAssistantResponse = last( - messages.filter((message) => message.message.role === MessageRole.Assistant) - ); + const lastAssistantResponse = getLastMessageOfType(messages, MessageRole.Assistant); useEffect(() => { next(initialMessagesRef.current); @@ -122,6 +134,63 @@ function ChatContent({ ); } +function PromptEdit({ + initialPrompt, + onSend, + onCancel, +}: { + initialPrompt: string; + onSend: (updatedPrompt: string) => void; + onCancel: () => void; +}) { + const [prompt, setPrompt] = useState(initialPrompt); + + return ( + + + { + if (textarea) { + setTimeout(() => textarea.focus()); + } + }} + fullWidth={true} + defaultValue={prompt} + onChange={(ev) => { + setPrompt(ev.target.value); + }} + /> + + + + + + onSend(prompt)} + /> + + + ); +} + export interface InsightProps { messages: Message[]; title: string; @@ -129,7 +198,11 @@ export interface InsightProps { } export function Insight({ messages, title, dataTestSubj }: InsightProps) { + const [initialMessages, setInitialMessages] = useState(messages); + const [isEditingPrompt, setEditingPrompt] = useState(false); + const [isInsightOpen, setInsightOpen] = useState(false); const [hasOpened, setHasOpened] = useState(false); + const [isPromptUpdated, setIsPromptUpdated] = useState(false); const connectors = useGenAIConnectors(); @@ -142,18 +215,85 @@ export function Insight({ messages, title, dataTestSubj }: InsightProps) { [service] ); + const handleSend = (newPrompt: string) => { + const clonedMessages = cloneDeep(messages); + const userMessage = getLastMessageOfType(clonedMessages, MessageRole.User); + if (!userMessage) return false; + + userMessage.message.content = newPrompt; + setIsPromptUpdated(true); + setInitialMessages(clonedMessages); + setEditingPrompt(false); + return true; + }; + + const handleCancel = () => { + setEditingPrompt(false); + setInsightOpen(false); + setHasOpened(false); + }; + const { services: { http }, } = useKibana(); let children: React.ReactNode = null; - if (hasOpened && connectors.selectedConnector) { + if ( + connectors.selectedConnector && + ((!isInsightOpen && hasOpened) || (isInsightOpen && !isEditingPrompt)) + ) { children = ( - + {isPromptUpdated ? ( + <> + + + + {i18n.translate('xpack.observabilityAiAssistant.insightModifiedPrompt', { + defaultMessage: 'This insight has been modified.', + })} + + + + { + setIsPromptUpdated(false); + setHasOpened(false); + setInsightOpen(false); + setInitialMessages(messages); + }} + > + + {i18n.translate('xpack.observabilityAiAssistant.resetDefaultPrompt', { + defaultMessage: 'Reset to default', + })} + + + + + + + + + ) : null} + + + + ); + } else if (isEditingPrompt) { + children = ( + ); } else if (!connectors.loading && !connectors.connectors?.length) { @@ -166,11 +306,24 @@ export function Insight({ messages, title, dataTestSubj }: InsightProps) { { - setHasOpened((prevHasOpened) => prevHasOpened || isOpen); + setHasOpened((prevHasOpened) => { + if (isEditingPrompt) return false; + return prevHasOpened || isOpen; + }); + setInsightOpen(isOpen); }} - controls={} + controls={ + { + setEditingPrompt(true); + setInsightOpen(true); + }} + /> + } loading={connectors.loading || chatService.loading} dataTestSubj={dataTestSubj} + isOpen={isInsightOpen} > {chatService.value ? ( diff --git a/x-pack/plugins/observability_ai_assistant/public/components/insight/insight_base.stories.tsx b/x-pack/plugins/observability_ai_assistant/public/components/insight/insight_base.stories.tsx index f34bb0ce3034d..b0a09caa805ae 100644 --- a/x-pack/plugins/observability_ai_assistant/public/components/insight/insight_base.stories.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/components/insight/insight_base.stories.tsx @@ -12,12 +12,12 @@ import { FindActionResult } from '@kbn/actions-plugin/server'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { InsightBase as Component, InsightBaseProps } from './insight_base'; import { KibanaReactStorybookDecorator } from '../../utils/storybook_decorator'; -import { ConnectorSelectorBase } from '../connector_selector/connector_selector_base'; import { MessagePanel } from '../message_panel/message_panel'; import { MessageText } from '../message_panel/message_text'; import { FeedbackButtons } from '../feedback_buttons'; import { RegenerateResponseButton } from '../buttons/regenerate_response_button'; import { StartChatButton } from '../buttons/start_chat_button'; +import { ActionsMenu } from './actions_menu'; export default { component: Component, @@ -37,17 +37,18 @@ const defaultProps: InsightBaseProps = { ], loading: false, controls: ( - {}} - reloadConnectors={() => {}} + ] as FindActionResult[], + selectedConnector: 'gpt-4', + loading: false, + selectConnector: () => {}, + reloadConnectors: () => {}, + }} + onEditPrompt={() => {}} /> ), onToggle: () => {}, @@ -87,6 +88,7 @@ Morbi non faucibus massa. Aliquam sed augue in eros ornare luctus sit amet cursu } /> ), + isOpen: false, }; export const Insight = Template.bind({}); diff --git a/x-pack/plugins/observability_ai_assistant/public/components/insight/insight_base.tsx b/x-pack/plugins/observability_ai_assistant/public/components/insight/insight_base.tsx index 702ce9296c132..62310b2aed555 100644 --- a/x-pack/plugins/observability_ai_assistant/public/components/insight/insight_base.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/components/insight/insight_base.tsx @@ -12,6 +12,7 @@ import { EuiContextMenuPanel, EuiFlexGroup, EuiFlexItem, + EuiIconTip, EuiPanel, EuiPopover, EuiSpacer, @@ -30,6 +31,7 @@ export interface InsightBaseProps { actions?: Array<{ id: string; label: string; icon?: string; handler: () => void }>; onToggle: (isOpen: boolean) => void; children: React.ReactNode; + isOpen: boolean; loading?: boolean; dataTestSubj?: string; } @@ -44,6 +46,7 @@ export function InsightBase({ actions, onToggle, loading, + isOpen, dataTestSubj = 'obsAiAssistantInsightButton', }: InsightBaseProps) { const { euiTheme } = useEuiTheme(); @@ -66,10 +69,19 @@ export function InsightBase({
- -
{title}
-
+ + +
{title}
+
+ +
{description} @@ -78,6 +90,7 @@ export function InsightBase({ } isLoading={loading} isDisabled={loading} + forceState={isOpen ? 'open' : 'closed'} extraAction={ actions?.length || controls ? ( diff --git a/x-pack/plugins/observability_shared/public/components/load_when_in_view/get_load_when_in_view_lazy.tsx b/x-pack/plugins/observability_shared/public/components/load_when_in_view/get_load_when_in_view_lazy.tsx index eb83b2682f341..b1cba9b58e182 100644 --- a/x-pack/plugins/observability_shared/public/components/load_when_in_view/get_load_when_in_view_lazy.tsx +++ b/x-pack/plugins/observability_shared/public/components/load_when_in_view/get_load_when_in_view_lazy.tsx @@ -7,7 +7,7 @@ import React, { lazy, Suspense } from 'react'; import { EuiLoadingSpinner } from '@elastic/eui'; -import { LoadWhenInViewProps } from './load_when_in_view'; +import type { LoadWhenInViewProps } from './load_when_in_view'; const LoadWhenInViewLazy = lazy(() => import('./load_when_in_view')); diff --git a/x-pack/plugins/observability_shared/public/components/tags_list/tags_list.tsx b/x-pack/plugins/observability_shared/public/components/tags_list/tags_list.tsx new file mode 100644 index 0000000000000..6599a1eec32ff --- /dev/null +++ b/x-pack/plugins/observability_shared/public/components/tags_list/tags_list.tsx @@ -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 React, { useState } from 'react'; +import { EuiBadge, EuiFlexGroup, EuiFlexItem, EuiText, EuiToolTip } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { EuiBadgeProps } from '@elastic/eui/src/components/badge/badge'; + +export interface TagsListProps { + onClick?: (tag: string) => void; + tags?: string[]; + numberOfTagsToDisplay?: number; + color?: EuiBadgeProps['color']; + ignoreEmpty?: boolean; +} + +const getFilterLabel = (tag: string) => { + return i18n.translate('xpack.observabilityShared.getFilterLabel.filter', { + defaultMessage: 'Click to filter list with tag {tag}', + values: { + tag, + }, + }); +}; + +const TagsList = ({ + ignoreEmpty, + tags, + numberOfTagsToDisplay = 3, + onClick, + color = 'hollow', +}: TagsListProps) => { + const [toDisplay, setToDisplay] = useState(numberOfTagsToDisplay); + + if (!tags || tags.length === 0) { + if (ignoreEmpty) { + return null; + } + return ( + + {i18n.translate('xpack.observabilityShared.tagsList.TextLabel', { defaultMessage: '--' })} + + ); + } + + const tagsToDisplay = tags.slice(0, toDisplay); + + return ( + + {tagsToDisplay.map((tag) => ( + // filtering only makes sense in monitor list, where we have summary + + {onClick ? ( + { + onClick(tag); + }} + onClickAriaLabel={getFilterLabel(tag)} + color={color} + className="eui-textTruncate" + style={{ maxWidth: 120 }} + > + {tag} + + ) : ( + + {tag} + + )} + + ))} + {tags.length > toDisplay && ( + + + {tags.slice(toDisplay, tags.length).map((tag) => ( + + {tag} + + ))} + + } + > + { + setToDisplay(tags.length); + }} + onClickAriaLabel={EXPAND_TAGS_LABEL} + > + +{tags.length - toDisplay} + + + + )} + {toDisplay > 3 && ( + + + { + setToDisplay(3); + }} + onClickAriaLabel={COLLAPSE_TAGS_LABEL} + > + -{tags.length - 3} + + + + )} + + ); +}; + +// eslint-disable-next-line import/no-default-export +export default TagsList; + +const EXPAND_TAGS_LABEL = i18n.translate('xpack.observabilityShared.tagsList.expand', { + defaultMessage: 'Click to view remaining tags', +}); + +const COLLAPSE_TAGS_LABEL = i18n.translate('xpack.observabilityShared.tagsList.collapse', { + defaultMessage: 'Click to collapse tags', +}); diff --git a/x-pack/plugins/observability_shared/public/components/tags_list/tags_list_lazy.tsx b/x-pack/plugins/observability_shared/public/components/tags_list/tags_list_lazy.tsx new file mode 100644 index 0000000000000..6e161cda52686 --- /dev/null +++ b/x-pack/plugins/observability_shared/public/components/tags_list/tags_list_lazy.tsx @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { lazy, Suspense } from 'react'; +import { EuiLoadingSpinner } from '@elastic/eui'; +import type { TagsListProps } from './tags_list'; + +const TagsListLazy = lazy(() => import('./tags_list')); + +export function TagsList(props: TagsListProps) { + return ( + }> + + + ); +} diff --git a/x-pack/plugins/observability_shared/public/index.ts b/x-pack/plugins/observability_shared/public/index.ts index 3c9d2fe5a8eec..9b4ef62271697 100644 --- a/x-pack/plugins/observability_shared/public/index.ts +++ b/x-pack/plugins/observability_shared/public/index.ts @@ -35,6 +35,8 @@ export { } from './components/section/section'; export type { SectionLinkProps } from './components/section/section'; export { LoadWhenInView } from './components/load_when_in_view/get_load_when_in_view_lazy'; +export { TagsList } from './components/tags_list/tags_list_lazy'; +export type { TagsListProps } from './components/tags_list/tags_list'; export { TechnicalPreviewBadge } from './components/technical_preview_badge/technical_preview_badge'; diff --git a/x-pack/plugins/osquery/cypress/e2e/tiers/endpoint_complete.cy.ts b/x-pack/plugins/osquery/cypress/e2e/tiers/endpoint_complete.cy.ts index 87cece1de8da2..104ff9c1dacfa 100644 --- a/x-pack/plugins/osquery/cypress/e2e/tiers/endpoint_complete.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/tiers/endpoint_complete.cy.ts @@ -7,7 +7,8 @@ import { checkOsqueryResponseActionsPermissions } from '../../tasks/response_actions'; -describe( +// FLAKY: https://github.com/elastic/kibana/issues/170469 +describe.skip( 'App Features for Enpoint Complete PLI', { tags: ['@serverless'], diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.test.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.test.ts index afed418ec8d3d..2367167b49697 100644 --- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.test.ts +++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.test.ts @@ -208,6 +208,7 @@ describe('createLifecycleExecutor', () => { flappingHistory: [], flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, TEST_ALERT_1: { alertId: 'TEST_ALERT_1', @@ -216,6 +217,7 @@ describe('createLifecycleExecutor', () => { flappingHistory: [], flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, }, trackedAlertsRecovered: {}, @@ -358,6 +360,7 @@ describe('createLifecycleExecutor', () => { flappingHistory: [], flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, TEST_ALERT_1: { alertId: 'TEST_ALERT_1', @@ -366,6 +369,7 @@ describe('createLifecycleExecutor', () => { flappingHistory: [], flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, }, trackedAlertsRecovered: {}, @@ -490,6 +494,7 @@ describe('createLifecycleExecutor', () => { flappingHistory: [], flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, TEST_ALERT_1: { alertId: 'TEST_ALERT_1', @@ -498,6 +503,7 @@ describe('createLifecycleExecutor', () => { flappingHistory: [], flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, }, trackedAlertsRecovered: {}, @@ -732,6 +738,7 @@ describe('createLifecycleExecutor', () => { flappingHistory: [], flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, TEST_ALERT_1: { alertId: 'TEST_ALERT_1', @@ -740,6 +747,7 @@ describe('createLifecycleExecutor', () => { flappingHistory: [], flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, }, trackedAlertsRecovered: {}, @@ -850,6 +858,7 @@ describe('createLifecycleExecutor', () => { flappingHistory: [], flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, TEST_ALERT_1: { alertId: 'TEST_ALERT_1', @@ -858,6 +867,7 @@ describe('createLifecycleExecutor', () => { flappingHistory: [], flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, }, trackedAlerts: {}, @@ -963,6 +973,7 @@ describe('createLifecycleExecutor', () => { flappingHistory: [], flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, TEST_ALERT_1: { alertId: 'TEST_ALERT_1', @@ -971,6 +982,7 @@ describe('createLifecycleExecutor', () => { flappingHistory: [], flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, }, trackedAlertsRecovered: {}, @@ -1077,6 +1089,7 @@ describe('createLifecycleExecutor', () => { flappingHistory: [], flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, }, trackedAlertsRecovered: { @@ -1087,6 +1100,7 @@ describe('createLifecycleExecutor', () => { flappingHistory: [], flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, }, }, @@ -1258,6 +1272,7 @@ describe('createLifecycleExecutor', () => { flappingHistory: [], flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, TEST_ALERT_1: { alertId: 'TEST_ALERT_1', @@ -1266,6 +1281,7 @@ describe('createLifecycleExecutor', () => { flappingHistory: [], flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, }, trackedAlertsRecovered: {}, @@ -1388,6 +1404,7 @@ describe('createLifecycleExecutor', () => { flappingHistory: [], flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, TEST_ALERT_1: { alertId: 'TEST_ALERT_1', @@ -1396,6 +1413,7 @@ describe('createLifecycleExecutor', () => { flappingHistory: [], flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, }, trackedAlertsRecovered: {}, @@ -1570,6 +1588,7 @@ describe('createLifecycleExecutor', () => { flappingHistory: flapping, flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, TEST_ALERT_1: { alertId: 'TEST_ALERT_1', @@ -1578,6 +1597,7 @@ describe('createLifecycleExecutor', () => { flappingHistory: [false, false], flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, TEST_ALERT_2: { alertId: 'TEST_ALERT_2', @@ -1586,6 +1606,7 @@ describe('createLifecycleExecutor', () => { flappingHistory: flapping, flapping: true, pendingRecoveredCount: 0, + activeCount: 0, }, TEST_ALERT_3: { alertId: 'TEST_ALERT_3', @@ -1594,6 +1615,7 @@ describe('createLifecycleExecutor', () => { flappingHistory: [false, false], flapping: true, pendingRecoveredCount: 0, + activeCount: 0, }, }, trackedAlertsRecovered: {}, @@ -1604,6 +1626,7 @@ describe('createLifecycleExecutor', () => { expect(serializedAlerts.state.trackedAlerts).toEqual({ TEST_ALERT_0: { + activeCount: 1, alertId: 'TEST_ALERT_0', alertUuid: 'TEST_ALERT_0_UUID', flapping: true, @@ -1612,6 +1635,7 @@ describe('createLifecycleExecutor', () => { started: '2020-01-01T12:00:00.000Z', }, TEST_ALERT_1: { + activeCount: 1, alertId: 'TEST_ALERT_1', alertUuid: 'TEST_ALERT_1_UUID', flapping: false, @@ -1620,6 +1644,7 @@ describe('createLifecycleExecutor', () => { started: '2020-01-02T12:00:00.000Z', }, TEST_ALERT_2: { + activeCount: 1, alertId: 'TEST_ALERT_2', alertUuid: 'TEST_ALERT_2_UUID', flapping: true, @@ -1628,6 +1653,7 @@ describe('createLifecycleExecutor', () => { started: '2020-01-01T12:00:00.000Z', }, TEST_ALERT_3: { + activeCount: 1, alertId: 'TEST_ALERT_3', alertUuid: 'TEST_ALERT_3_UUID', flapping: true, @@ -1786,6 +1812,7 @@ describe('createLifecycleExecutor', () => { flappingHistory: [true, true, true, true], flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, TEST_ALERT_1: { alertId: 'TEST_ALERT_1', @@ -1794,6 +1821,7 @@ describe('createLifecycleExecutor', () => { flappingHistory: notFlapping, flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, TEST_ALERT_2: { alertId: 'TEST_ALERT_2', @@ -1802,6 +1830,7 @@ describe('createLifecycleExecutor', () => { flappingHistory: [true, true], flapping: true, pendingRecoveredCount: 0, + activeCount: 0, }, TEST_ALERT_3: { alertId: 'TEST_ALERT_3', @@ -1810,6 +1839,7 @@ describe('createLifecycleExecutor', () => { flappingHistory: notFlapping, flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, }, trackedAlertsRecovered: {}, @@ -1820,6 +1850,7 @@ describe('createLifecycleExecutor', () => { expect(serializedAlerts.state.trackedAlerts).toEqual({ TEST_ALERT_2: { + activeCount: 0, alertId: 'TEST_ALERT_2', alertUuid: 'TEST_ALERT_2_UUID', flapping: true, @@ -1831,6 +1862,7 @@ describe('createLifecycleExecutor', () => { expect(serializedAlerts.state.trackedAlertsRecovered).toEqual({ TEST_ALERT_0: { + activeCount: 0, alertId: 'TEST_ALERT_0', alertUuid: 'TEST_ALERT_0_UUID', flapping: true, @@ -1839,6 +1871,7 @@ describe('createLifecycleExecutor', () => { started: '2020-01-01T12:00:00.000Z', }, TEST_ALERT_1: { + activeCount: 0, alertId: 'TEST_ALERT_1', alertUuid: 'TEST_ALERT_1_UUID', flapping: false, @@ -1847,6 +1880,7 @@ describe('createLifecycleExecutor', () => { started: '2020-01-02T12:00:00.000Z', }, TEST_ALERT_3: { + activeCount: 0, alertId: 'TEST_ALERT_3', alertUuid: 'TEST_ALERT_3_UUID', flapping: false, diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts index 91d30fae7b3dc..4bd3b912ae67d 100644 --- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts +++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts @@ -290,7 +290,7 @@ export const createLifecycleExecutor = trackedAlertRecoveredIds ); - const { alertUuid, started, flapping, pendingRecoveredCount } = !isNew + const { alertUuid, started, flapping, pendingRecoveredCount, activeCount } = !isNew ? state.trackedAlerts[alertId] : { alertUuid: lifecycleAlertServices.getAlertUuid(alertId), @@ -299,6 +299,7 @@ export const createLifecycleExecutor = ? state.trackedAlertsRecovered[alertId].flapping : false, pendingRecoveredCount: 0, + activeCount: 0, }; const event: ParsedTechnicalFields & ParsedExperimentalFields = { @@ -342,16 +343,20 @@ export const createLifecycleExecutor = flappingHistory, flapping, pendingRecoveredCount, + activeCount, }; }); const trackedEventsToIndex = makeEventsDataMapFor(trackedAlertIds); const newEventsToIndex = makeEventsDataMapFor(newAlertIds); const trackedRecoveredEventsToIndex = makeEventsDataMapFor(trackedAlertRecoveredIds); - const allEventsToIndex = [ - ...getAlertsForNotification(flappingSettings, trackedEventsToIndex), - ...newEventsToIndex, - ]; + const allEventsToIndex = getAlertsForNotification( + flappingSettings, + rule.alertDelay?.active ?? 0, + trackedEventsToIndex, + newEventsToIndex, + { maintenanceWindowIds, timestamp: commonRuleFields[TIMESTAMP] } + ); // Only write alerts if: // - writing is enabled @@ -392,18 +397,34 @@ export const createLifecycleExecutor = } const nextTrackedAlerts = Object.fromEntries( - allEventsToIndex + [...newEventsToIndex, ...trackedEventsToIndex] .filter(({ event }) => event[ALERT_STATUS] !== ALERT_STATUS_RECOVERED) - .map(({ event, flappingHistory, flapping: isCurrentlyFlapping, pendingRecoveredCount }) => { - const alertId = event[ALERT_INSTANCE_ID]!; - const alertUuid = event[ALERT_UUID]!; - const started = new Date(event[ALERT_START]!).toISOString(); - const flapping = isFlapping(flappingSettings, flappingHistory, isCurrentlyFlapping); - return [ - alertId, - { alertId, alertUuid, started, flappingHistory, flapping, pendingRecoveredCount }, - ]; - }) + .map( + ({ + event, + flappingHistory, + flapping: isCurrentlyFlapping, + pendingRecoveredCount, + activeCount, + }) => { + const alertId = event[ALERT_INSTANCE_ID]!; + const alertUuid = event[ALERT_UUID]!; + const started = new Date(event[ALERT_START]!).toISOString(); + const flapping = isFlapping(flappingSettings, flappingHistory, isCurrentlyFlapping); + return [ + alertId, + { + alertId, + alertUuid, + started, + flappingHistory, + flapping, + pendingRecoveredCount, + activeCount, + }, + ]; + } + ) ); const nextTrackedAlertsRecovered = Object.fromEntries( @@ -416,16 +437,32 @@ export const createLifecycleExecutor = event[ALERT_STATUS] === ALERT_STATUS_RECOVERED && (flapping || flappingHistory.filter((f: boolean) => f).length > 0) ) - .map(({ event, flappingHistory, flapping: isCurrentlyFlapping, pendingRecoveredCount }) => { - const alertId = event[ALERT_INSTANCE_ID]!; - const alertUuid = event[ALERT_UUID]!; - const started = new Date(event[ALERT_START]!).toISOString(); - const flapping = isFlapping(flappingSettings, flappingHistory, isCurrentlyFlapping); - return [ - alertId, - { alertId, alertUuid, started, flappingHistory, flapping, pendingRecoveredCount }, - ]; - }) + .map( + ({ + event, + flappingHistory, + flapping: isCurrentlyFlapping, + pendingRecoveredCount, + activeCount, + }) => { + const alertId = event[ALERT_INSTANCE_ID]!; + const alertUuid = event[ALERT_UUID]!; + const started = new Date(event[ALERT_START]!).toISOString(); + const flapping = isFlapping(flappingSettings, flappingHistory, isCurrentlyFlapping); + return [ + alertId, + { + alertId, + alertUuid, + started, + flappingHistory, + flapping, + pendingRecoveredCount, + activeCount, + }, + ]; + } + ) ); return { diff --git a/x-pack/plugins/rule_registry/server/utils/get_alerts_for_notification.test.ts b/x-pack/plugins/rule_registry/server/utils/get_alerts_for_notification.test.ts index b3047303bcb08..abb9ebba6d016 100644 --- a/x-pack/plugins/rule_registry/server/utils/get_alerts_for_notification.test.ts +++ b/x-pack/plugins/rule_registry/server/utils/get_alerts_for_notification.test.ts @@ -14,12 +14,17 @@ import { cloneDeep } from 'lodash'; import { getAlertsForNotification } from './get_alerts_for_notification'; describe('getAlertsForNotification', () => { + const newEventParams = { + maintenanceWindowIds: ['maintenance-window-id'], + timestamp: 'timestamp', + }; const alert1 = { event: { 'kibana.alert.status': ALERT_STATUS_RECOVERED, }, flapping: true, pendingRecoveredCount: 3, + activeCount: 3, }; const alert2 = { event: { @@ -40,13 +45,38 @@ describe('getAlertsForNotification', () => { pendingRecoveredCount: 4, flappingHistory: [true, true], }; + const alert5 = { + event: { + 'kibana.alert.status': ALERT_STATUS_ACTIVE, + }, + activeCount: 1, + pendingRecoveredCount: 0, + flappingHistory: [], + }; test('should set pendingRecoveredCount to zero for all active alerts', () => { - const trackedEvents = [alert4]; - expect(getAlertsForNotification(DEFAULT_FLAPPING_SETTINGS, trackedEvents)) - .toMatchInlineSnapshot(` + const trackedEvents = cloneDeep([alert4]); + const newEvents = cloneDeep([alert5]); + expect( + getAlertsForNotification( + DEFAULT_FLAPPING_SETTINGS, + 0, + trackedEvents, + newEvents, + newEventParams + ) + ).toMatchInlineSnapshot(` Array [ Object { + "activeCount": 2, + "event": Object { + "kibana.alert.status": "active", + }, + "flappingHistory": Array [], + "pendingRecoveredCount": 0, + }, + Object { + "activeCount": 1, "event": Object { "kibana.alert.status": "active", }, @@ -62,10 +92,12 @@ describe('getAlertsForNotification', () => { test('should not remove alerts if the num of recovered alerts is not at the limit', () => { const trackedEvents = cloneDeep([alert1, alert2, alert3]); - expect(getAlertsForNotification(DEFAULT_FLAPPING_SETTINGS, trackedEvents)) - .toMatchInlineSnapshot(` + expect( + getAlertsForNotification(DEFAULT_FLAPPING_SETTINGS, 0, trackedEvents, [], newEventParams) + ).toMatchInlineSnapshot(` Array [ Object { + "activeCount": 0, "event": Object { "kibana.alert.status": "recovered", }, @@ -73,12 +105,14 @@ describe('getAlertsForNotification', () => { "pendingRecoveredCount": 0, }, Object { + "activeCount": 0, "event": Object { "kibana.alert.status": "recovered", }, "flapping": false, }, Object { + "activeCount": 0, "event": Object { "event.action": "active", "kibana.alert.status": "active", @@ -92,10 +126,12 @@ describe('getAlertsForNotification', () => { test('should reset counts and not modify alerts if flapping is disabled', () => { const trackedEvents = cloneDeep([alert1, alert2, alert3]); - expect(getAlertsForNotification(DISABLE_FLAPPING_SETTINGS, trackedEvents)) - .toMatchInlineSnapshot(` + expect( + getAlertsForNotification(DISABLE_FLAPPING_SETTINGS, 0, trackedEvents, [], newEventParams) + ).toMatchInlineSnapshot(` Array [ Object { + "activeCount": 0, "event": Object { "kibana.alert.status": "recovered", }, @@ -103,6 +139,7 @@ describe('getAlertsForNotification', () => { "pendingRecoveredCount": 0, }, Object { + "activeCount": 0, "event": Object { "kibana.alert.status": "recovered", }, @@ -110,6 +147,7 @@ describe('getAlertsForNotification', () => { "pendingRecoveredCount": 0, }, Object { + "activeCount": 0, "event": Object { "kibana.alert.status": "recovered", }, @@ -119,4 +157,106 @@ describe('getAlertsForNotification', () => { ] `); }); + + test('should increment activeCount for all active alerts', () => { + const trackedEvents = cloneDeep([alert4]); + const newEvents = cloneDeep([alert5]); + expect( + getAlertsForNotification( + DEFAULT_FLAPPING_SETTINGS, + 0, + trackedEvents, + newEvents, + newEventParams + ) + ).toMatchInlineSnapshot(` + Array [ + Object { + "activeCount": 2, + "event": Object { + "kibana.alert.status": "active", + }, + "flappingHistory": Array [], + "pendingRecoveredCount": 0, + }, + Object { + "activeCount": 1, + "event": Object { + "kibana.alert.status": "active", + }, + "flappingHistory": Array [ + true, + true, + ], + "pendingRecoveredCount": 0, + }, + ] + `); + }); + + test('should reset activeCount for all recovered alerts', () => { + const trackedEvents = cloneDeep([alert1, alert2]); + expect( + getAlertsForNotification(DEFAULT_FLAPPING_SETTINGS, 0, trackedEvents, [], newEventParams) + ).toMatchInlineSnapshot(` + Array [ + Object { + "activeCount": 0, + "event": Object { + "kibana.alert.status": "recovered", + }, + "flapping": true, + "pendingRecoveredCount": 0, + }, + Object { + "activeCount": 0, + "event": Object { + "kibana.alert.status": "recovered", + }, + "flapping": false, + }, + ] + `); + }); + + test('should not return active alerts if the activeCount is less than the rule alertDelay', () => { + const trackedEvents = cloneDeep([alert4]); + const newEvents = cloneDeep([alert5]); + expect( + getAlertsForNotification( + DEFAULT_FLAPPING_SETTINGS, + 5, + trackedEvents, + newEvents, + newEventParams + ) + ).toMatchInlineSnapshot(`Array []`); + }); + + test('should update active alert to look like a new alert if the activeCount is equal to the rule alertDelay', () => { + const trackedEvents = cloneDeep([alert5]); + expect( + getAlertsForNotification(DEFAULT_FLAPPING_SETTINGS, 2, trackedEvents, [], newEventParams) + ).toMatchInlineSnapshot(` + Array [ + Object { + "activeCount": 2, + "event": Object { + "event.action": "open", + "kibana.alert.duration.us": 0, + "kibana.alert.maintenance_window_ids": Array [ + "maintenance-window-id", + ], + "kibana.alert.start": "timestamp", + "kibana.alert.status": "active", + "kibana.alert.time_range": Object { + "gte": "timestamp", + }, + }, + "flappingHistory": Array [], + "pendingRecoveredCount": 0, + }, + ] + `); + }); }); diff --git a/x-pack/plugins/rule_registry/server/utils/get_alerts_for_notification.ts b/x-pack/plugins/rule_registry/server/utils/get_alerts_for_notification.ts index 878db2a918022..5ec0e5b835eec 100644 --- a/x-pack/plugins/rule_registry/server/utils/get_alerts_for_notification.ts +++ b/x-pack/plugins/rule_registry/server/utils/get_alerts_for_notification.ts @@ -11,32 +11,68 @@ import { ALERT_STATUS, ALERT_STATUS_ACTIVE, ALERT_STATUS_RECOVERED, + ALERT_START, + ALERT_DURATION, EVENT_ACTION, + ALERT_TIME_RANGE, + ALERT_MAINTENANCE_WINDOW_IDS, } from '@kbn/rule-data-utils'; export function getAlertsForNotification( flappingSettings: RulesSettingsFlappingProperties, - trackedEventsToIndex: any[] + alertDelay: number, + trackedEventsToIndex: any[], + newEventsToIndex: any[], + newEventParams: { + // values used to create a new event + maintenanceWindowIds?: string[]; + timestamp: string; + } ) { - return trackedEventsToIndex.map((trackedEvent) => { - if (!flappingSettings.enabled || trackedEvent.event[ALERT_STATUS] === ALERT_STATUS_ACTIVE) { + const events: any[] = []; + for (const trackedEvent of [...newEventsToIndex, ...trackedEventsToIndex]) { + if (trackedEvent.event[ALERT_STATUS] === ALERT_STATUS_ACTIVE) { + const count = trackedEvent.activeCount || 0; + trackedEvent.activeCount = count + 1; trackedEvent.pendingRecoveredCount = 0; - } else if ( - flappingSettings.enabled && - trackedEvent.event[ALERT_STATUS] === ALERT_STATUS_RECOVERED - ) { - if (trackedEvent.flapping) { - const count = trackedEvent.pendingRecoveredCount || 0; - trackedEvent.pendingRecoveredCount = count + 1; - if (trackedEvent.pendingRecoveredCount < flappingSettings.statusChangeThreshold) { - trackedEvent.event[ALERT_STATUS] = ALERT_STATUS_ACTIVE; - trackedEvent.event[EVENT_ACTION] = 'active'; - delete trackedEvent.event[ALERT_END]; - } else { - trackedEvent.pendingRecoveredCount = 0; + // do not index the event if the number of consecutive + // active alerts is less than the rule alertDelay threshold + if (trackedEvent.activeCount < alertDelay) { + // remove from array of events to index + continue; + } else { + const { timestamp, maintenanceWindowIds } = newEventParams; + // if the active count is equal to the alertDelay it is considered a new event + if (trackedEvent.activeCount === alertDelay) { + // update the event to look like a new event + trackedEvent.event[ALERT_DURATION] = 0; + trackedEvent.event[ALERT_START] = timestamp; + trackedEvent.event[ALERT_TIME_RANGE] = { gte: timestamp }; + trackedEvent.event[EVENT_ACTION] = 'open'; + if (maintenanceWindowIds?.length) { + trackedEvent.event[ALERT_MAINTENANCE_WINDOW_IDS] = maintenanceWindowIds; + } } } + } else if (trackedEvent.event[ALERT_STATUS] === ALERT_STATUS_RECOVERED) { + trackedEvent.activeCount = 0; + if (flappingSettings.enabled) { + if (trackedEvent.flapping) { + const count = trackedEvent.pendingRecoveredCount || 0; + trackedEvent.pendingRecoveredCount = count + 1; + if (trackedEvent.pendingRecoveredCount < flappingSettings.statusChangeThreshold) { + trackedEvent.event[ALERT_STATUS] = ALERT_STATUS_ACTIVE; + trackedEvent.event[EVENT_ACTION] = 'active'; + delete trackedEvent.event[ALERT_END]; + } else { + trackedEvent.pendingRecoveredCount = 0; + } + } + } else { + trackedEvent.pendingRecoveredCount = 0; + } } - return trackedEvent; - }); + events.push(trackedEvent); + } + return events; } diff --git a/x-pack/plugins/rule_registry/server/utils/get_updated_flapping_history.test.ts b/x-pack/plugins/rule_registry/server/utils/get_updated_flapping_history.test.ts index 52467f168e641..84685779186d9 100644 --- a/x-pack/plugins/rule_registry/server/utils/get_updated_flapping_history.test.ts +++ b/x-pack/plugins/rule_registry/server/utils/get_updated_flapping_history.test.ts @@ -49,6 +49,7 @@ describe('getUpdatedFlappingHistory', () => { flappingHistory: [], flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, }, trackedAlertsRecovered: {}, @@ -81,6 +82,7 @@ describe('getUpdatedFlappingHistory', () => { flappingHistory: [], flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, }, trackedAlerts: {}, @@ -115,6 +117,7 @@ describe('getUpdatedFlappingHistory', () => { flappingHistory: [], flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, }, trackedAlertsRecovered: {}, @@ -150,6 +153,7 @@ describe('getUpdatedFlappingHistory', () => { flappingHistory: [], flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, }, }; @@ -184,6 +188,7 @@ describe('getUpdatedFlappingHistory', () => { flappingHistory: [], flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, }, }; diff --git a/x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/integration_tests/pdfmaker.test.ts b/x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/integration_tests/pdfmaker.test.ts index 2243f68b7ad71..9643a3bbcd4a1 100644 --- a/x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/integration_tests/pdfmaker.test.ts +++ b/x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/integration_tests/pdfmaker.test.ts @@ -33,6 +33,7 @@ describe('PdfMaker', () => { branch: 'screenshot-test', buildNum: 567891011, buildSha: 'screenshot-dfdfed0a', + buildShaShort: 'scr-dfdfed0a', dist: false, version: '1000.0.0', buildDate: new Date('2023-05-15T23:12:09.000Z'), diff --git a/x-pack/plugins/screenshotting/server/screenshots/index.test.ts b/x-pack/plugins/screenshotting/server/screenshots/index.test.ts index 4a177a94a147b..3c6540f287706 100644 --- a/x-pack/plugins/screenshotting/server/screenshots/index.test.ts +++ b/x-pack/plugins/screenshotting/server/screenshots/index.test.ts @@ -56,6 +56,7 @@ describe('Screenshot Observable Pipeline', () => { branch: 'screenshot-test', buildNum: 567891011, buildSha: 'screenshot-dfdfed0a', + buildShaShort: 'scrn-dfdfed0a', dist: false, version: '5000.0.0', buildDate: new Date('2023-05-15T23:12:09.000Z'), diff --git a/x-pack/plugins/security/server/__snapshots__/prompt_page.test.tsx.snap b/x-pack/plugins/security/server/__snapshots__/prompt_page.test.tsx.snap index 0ecd0ae131ad9..fe6552fa889af 100644 --- a/x-pack/plugins/security/server/__snapshots__/prompt_page.test.tsx.snap +++ b/x-pack/plugins/security/server/__snapshots__/prompt_page.test.tsx.snap @@ -1,5 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`PromptPage renders as expected with additional scripts 1`] = `"ElasticMockedFonts

Some Title

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

Some Title

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

Some Title

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

Some Title

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

We hit an authentication error

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

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

We hit an authentication error

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

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

We hit an authentication error

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

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

We hit an authentication error

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

"`; diff --git a/x-pack/plugins/security/server/authentication/authentication_service.test.ts b/x-pack/plugins/security/server/authentication/authentication_service.test.ts index a779d30891b86..3f985df49cef1 100644 --- a/x-pack/plugins/security/server/authentication/authentication_service.test.ts +++ b/x-pack/plugins/security/server/authentication/authentication_service.test.ts @@ -19,6 +19,7 @@ import type { ElasticsearchServiceSetup, HttpServiceSetup, HttpServiceStart, + IStaticAssets, KibanaRequest, Logger, LoggerFactory, @@ -63,7 +64,7 @@ describe('AuthenticationService', () => { elasticsearch: jest.Mocked; config: ConfigType; license: jest.Mocked; - buildNumber: number; + staticAssets: IStaticAssets; customBranding: jest.Mocked; }; let mockStartAuthenticationParams: { @@ -96,7 +97,7 @@ describe('AuthenticationService', () => { isTLSEnabled: false, }), license: licenseMock.create(), - buildNumber: 100500, + staticAssets: coreSetupMock.http.staticAssets, customBranding: customBrandingServiceMock.createSetupContract(), }; mockCanRedirectRequest.mockReturnValue(false); @@ -983,7 +984,7 @@ describe('AuthenticationService', () => { expect(mockRenderUnauthorizedPage).toHaveBeenCalledWith({ basePath: mockSetupAuthenticationParams.http.basePath, - buildNumber: 100500, + staticAssets: expect.any(Object), originalURL: '/mock-server-basepath/app/some', }); }); @@ -1015,7 +1016,7 @@ describe('AuthenticationService', () => { expect(mockRenderUnauthorizedPage).toHaveBeenCalledWith({ basePath: mockSetupAuthenticationParams.http.basePath, - buildNumber: 100500, + staticAssets: expect.any(Object), originalURL: '/mock-server-basepath/app/some', }); }); @@ -1050,7 +1051,7 @@ describe('AuthenticationService', () => { expect(mockRenderUnauthorizedPage).toHaveBeenCalledWith({ basePath: mockSetupAuthenticationParams.http.basePath, - buildNumber: 100500, + staticAssets: expect.any(Object), originalURL: '/mock-server-basepath/', }); }); diff --git a/x-pack/plugins/security/server/authentication/authentication_service.ts b/x-pack/plugins/security/server/authentication/authentication_service.ts index d6f955b8b4558..cbe4e5f965691 100644 --- a/x-pack/plugins/security/server/authentication/authentication_service.ts +++ b/x-pack/plugins/security/server/authentication/authentication_service.ts @@ -40,12 +40,14 @@ import type { Session } from '../session_management'; import type { UserProfileServiceStartInternal } from '../user_profile'; interface AuthenticationServiceSetupParams { - http: Pick; + http: Pick< + HttpServiceSetup, + 'basePath' | 'csp' | 'registerAuth' | 'registerOnPreResponse' | 'staticAssets' + >; customBranding: CustomBrandingSetup; elasticsearch: Pick; config: ConfigType; license: SecurityLicense; - buildNumber: number; } interface AuthenticationServiceStartParams { @@ -92,7 +94,6 @@ export class AuthenticationService { config, http, license, - buildNumber, elasticsearch, customBranding, }: AuthenticationServiceSetupParams) { @@ -204,8 +205,8 @@ export class AuthenticationService { }); return toolkit.render({ body: renderUnauthenticatedPage({ - buildNumber, basePath: http.basePath, + staticAssets: http.staticAssets, originalURL, customBranding: customBrandingValue, }), diff --git a/x-pack/plugins/security/server/authentication/unauthenticated_page.test.tsx b/x-pack/plugins/security/server/authentication/unauthenticated_page.test.tsx index d65c032911a03..0abd444f80365 100644 --- a/x-pack/plugins/security/server/authentication/unauthenticated_page.test.tsx +++ b/x-pack/plugins/security/server/authentication/unauthenticated_page.test.tsx @@ -26,7 +26,7 @@ describe('UnauthenticatedPage', () => { const body = renderToStaticMarkup( @@ -44,7 +44,7 @@ describe('UnauthenticatedPage', () => { const body = renderToStaticMarkup( diff --git a/x-pack/plugins/security/server/authentication/unauthenticated_page.tsx b/x-pack/plugins/security/server/authentication/unauthenticated_page.tsx index fce29bbe89bc3..694f8f16bba0d 100644 --- a/x-pack/plugins/security/server/authentication/unauthenticated_page.tsx +++ b/x-pack/plugins/security/server/authentication/unauthenticated_page.tsx @@ -11,6 +11,7 @@ import { renderToStaticMarkup } from 'react-dom/server'; import type { IBasePath } from '@kbn/core/server'; import type { CustomBranding } from '@kbn/core-custom-branding-common'; +import type { IStaticAssets } from '@kbn/core-http-server'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; @@ -18,16 +19,21 @@ import { PromptPage } from '../prompt_page'; interface Props { originalURL: string; - buildNumber: number; basePath: IBasePath; + staticAssets: IStaticAssets; customBranding: CustomBranding; } -export function UnauthenticatedPage({ basePath, originalURL, buildNumber, customBranding }: Props) { +export function UnauthenticatedPage({ + basePath, + originalURL, + staticAssets, + customBranding, +}: Props) { return ( ElasticMockedFonts

You do not have permission to access the requested page

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

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

You do not have permission to access the requested page

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

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

You do not have permission to access the requested page

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

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

You do not have permission to access the requested page

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

"`; diff --git a/x-pack/plugins/security/server/authorization/authorization_service.test.ts b/x-pack/plugins/security/server/authorization/authorization_service.test.ts index a052d67532480..ddc5e26903c2b 100644 --- a/x-pack/plugins/security/server/authorization/authorization_service.test.ts +++ b/x-pack/plugins/security/server/authorization/authorization_service.test.ts @@ -76,7 +76,6 @@ it(`#setup returns exposed services`, () => { loggers: loggingSystemMock.create(), kibanaIndexName, packageVersion: 'some-version', - buildNumber: 42, features: mockFeaturesSetup, getSpacesService: mockGetSpacesService, getCurrentUser: jest.fn(), @@ -138,7 +137,6 @@ describe('#start', () => { loggers: loggingSystemMock.create(), kibanaIndexName, packageVersion: 'some-version', - buildNumber: 42, features: featuresPluginMock.createSetup(), getSpacesService: jest .fn() @@ -211,7 +209,6 @@ it('#stop unsubscribes from license and ES updates.', async () => { loggers: loggingSystemMock.create(), kibanaIndexName, packageVersion: 'some-version', - buildNumber: 42, features: featuresPluginMock.createSetup(), getSpacesService: jest .fn() diff --git a/x-pack/plugins/security/server/authorization/authorization_service.tsx b/x-pack/plugins/security/server/authorization/authorization_service.tsx index 16f2ed3b446e1..795016874dc97 100644 --- a/x-pack/plugins/security/server/authorization/authorization_service.tsx +++ b/x-pack/plugins/security/server/authorization/authorization_service.tsx @@ -57,7 +57,6 @@ export { Actions } from './actions'; interface AuthorizationServiceSetupParams { packageVersion: string; - buildNumber: number; http: HttpServiceSetup; capabilities: CapabilitiesSetup; getClusterClient: () => Promise; @@ -100,7 +99,6 @@ export class AuthorizationService { http, capabilities, packageVersion, - buildNumber, getClusterClient, license, loggers, @@ -179,7 +177,7 @@ export class AuthorizationService { const next = `${http.basePath.get(request)}${request.url.pathname}${request.url.search}`; const body = renderToString( { const body = renderToStaticMarkup( @@ -44,7 +44,7 @@ describe('ResetSessionPage', () => { const body = renderToStaticMarkup( diff --git a/x-pack/plugins/security/server/authorization/reset_session_page.tsx b/x-pack/plugins/security/server/authorization/reset_session_page.tsx index 85c78ddfcbaec..27af66a8a4048 100644 --- a/x-pack/plugins/security/server/authorization/reset_session_page.tsx +++ b/x-pack/plugins/security/server/authorization/reset_session_page.tsx @@ -10,6 +10,7 @@ import React from 'react'; import type { IBasePath } from '@kbn/core/server'; import type { CustomBranding } from '@kbn/core-custom-branding-common'; +import type { IStaticAssets } from '@kbn/core-http-server'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; @@ -22,18 +23,18 @@ import { PromptPage } from '../prompt_page'; */ export function ResetSessionPage({ logoutUrl, - buildNumber, + staticAssets, basePath, customBranding, }: { logoutUrl: string; - buildNumber: number; + staticAssets: IStaticAssets; basePath: IBasePath; customBranding: CustomBranding; }) { return ( spaces?.spacesService, features, getCurrentUser: (request) => this.getAuthentication().getCurrentUser(request), diff --git a/x-pack/plugins/security/server/prompt_page.test.tsx b/x-pack/plugins/security/server/prompt_page.test.tsx index 268a7ac640e71..754584284035a 100644 --- a/x-pack/plugins/security/server/prompt_page.test.tsx +++ b/x-pack/plugins/security/server/prompt_page.test.tsx @@ -25,7 +25,7 @@ describe('PromptPage', () => { const body = renderToStaticMarkup( Some Body
} @@ -45,7 +45,7 @@ describe('PromptPage', () => { const body = renderToStaticMarkup( ; -export const EndpointParams = z.object({ +export type DefaultParams = z.infer; +export const DefaultParams = z.object({ command: z.literal('isolate'), comment: z.string().optional(), }); +export type ProcessesParams = z.infer; +export const ProcessesParams = z.object({ + command: z.enum(['kill-process', 'suspend-process']), + comment: z.string().optional(), + config: z.object({ + /** + * Field to use instead of process.pid + */ + field: z.string(), + /** + * Whether to overwrite field with process.pid + */ + overwrite: z.boolean().optional().default(true), + }), +}); + export type EndpointResponseAction = z.infer; export const EndpointResponseAction = z.object({ action_type_id: z.literal('.endpoint'), - params: EndpointParams, + params: z.union([DefaultParams, ProcessesParams]), }); export type RuleResponseEndpointAction = z.infer; export const RuleResponseEndpointAction = z.object({ actionTypeId: z.literal('.endpoint'), - params: EndpointParams, + params: z.union([DefaultParams, ProcessesParams]), }); export type ResponseAction = z.infer; diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_response_actions/response_actions.schema.yaml b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_response_actions/response_actions.schema.yaml index 3e4b37e115add..751a1efee8fa8 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_response_actions/response_actions.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_response_actions/response_actions.schema.yaml @@ -2,7 +2,7 @@ openapi: 3.0.0 info: title: Response Actions Schema version: 'not applicable' -paths: {} +paths: { } components: x-codegen-enabled: true schemas: @@ -113,7 +113,7 @@ components: - actionTypeId - params - EndpointParams: + DefaultParams: type: object properties: command: @@ -125,6 +125,32 @@ components: required: - command + ProcessesParams: + type: object + properties: + command: + type: string + enum: + - kill-process + - suspend-process + comment: + type: string + config: + required: + - field + type: object + properties: + field: + type: string + description: Field to use instead of process.pid + overwrite: + type: boolean + description: Whether to overwrite field with process.pid + default: true + required: + - command + - config + EndpointResponseAction: type: object properties: @@ -133,7 +159,9 @@ components: enum: - .endpoint params: - $ref: '#/components/schemas/EndpointParams' + oneOf: + - $ref: '#/components/schemas/DefaultParams' + - $ref: '#/components/schemas/ProcessesParams' required: - action_type_id - params @@ -147,7 +175,9 @@ components: enum: - .endpoint params: - $ref: '#/components/schemas/EndpointParams' + oneOf: + - $ref: '#/components/schemas/DefaultParams' + - $ref: '#/components/schemas/ProcessesParams' required: - actionTypeId - params diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema_legacy/response_actions.ts b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema_legacy/response_actions.ts index 8f176a9908041..e4164c6d2bb04 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema_legacy/response_actions.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema_legacy/response_actions.ts @@ -6,7 +6,6 @@ */ import { arrayQueries, ecsMapping } from '@kbn/osquery-io-ts-types'; import * as t from 'io-ts'; -import { ENABLED_AUTOMATED_RESPONSE_ACTION_COMMANDS } from '../../../../endpoint/service/response_actions/constants'; // to enable using RESPONSE_ACTION_API_COMMANDS_NAMES as a type function keyObject(arr: T): { [K in T[number]]: null } { @@ -15,7 +14,9 @@ function keyObject(arr: T): { [K in T[number]]: nul export type EndpointParams = t.TypeOf; export const EndpointParams = t.type({ - command: t.keyof(keyObject(ENABLED_AUTOMATED_RESPONSE_ACTION_COMMANDS)), + // TODO: TC- change these when we go GA with automated process actions + command: t.keyof(keyObject(['isolate', 'kill-process', 'suspend-process'])), + // command: t.keyof(keyObject(ENABLED_AUTOMATED_RESPONSE_ACTION_COMMANDS)), comment: t.union([t.string, t.undefined]), }); diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index 4acadbd8e39ea..cf9e5ed8ba61f 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -8,6 +8,7 @@ import { RuleNotifyWhen } from '@kbn/alerting-plugin/common'; import type { AddOptionsListControlProps } from '@kbn/controls-plugin/public'; import * as i18n from './translations'; + export { SecurityPageName } from '@kbn/security-solution-navigation'; /** @@ -338,12 +339,12 @@ export const ALERTS_AS_DATA_FIND_URL = `${ALERTS_AS_DATA_URL}/find` as const; export const UNAUTHENTICATED_USER = 'Unauthenticated' as const; /** - Licensing requirements + Licensing requirements */ export const MINIMUM_ML_LICENSE = 'platinum' as const; /** - Machine Learning constants + Machine Learning constants */ export const ML_GROUP_ID = 'security' as const; export const LEGACY_ML_GROUP_ID = 'siem' as const; @@ -519,3 +520,8 @@ export const DEFAULT_ALERT_TAGS_VALUE = [ * Max length for the comments within security solution */ export const MAX_COMMENT_LENGTH = 30000 as const; + +/** + * Cases external attachment IDs + */ +export const CASE_ATTACHMENT_ENDPOINT_TYPE_ID = 'endpoint' as const; 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 1b74ae55f6289..e89cb996f261f 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 @@ -287,6 +287,28 @@ export class EndpointRuleAlertGenerator extends BaseDataGenerator { comment: 'test', }, }, + { + params: { + command: 'suspend-process', + comment: 'Suspend host', + config: { + field: 'entity_id', + overwrite: false, + }, + }, + action_type_id: '.endpoint', + }, + { + params: { + command: 'kill-process', + comment: 'Kill host', + config: { + field: '', + overwrite: true, + }, + }, + action_type_id: '.endpoint', + }, ], rule_id: ELASTIC_SECURITY_RULE_ID, rule_name_override: 'message', diff --git a/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_endpoint_fleet_actions.ts b/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_endpoint_fleet_actions.ts index f94f148765ba5..5eb2cebcc3f8e 100644 --- a/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_endpoint_fleet_actions.ts +++ b/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_endpoint_fleet_actions.ts @@ -9,6 +9,7 @@ import type { Client } from '@elastic/elasticsearch'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { AGENT_ACTIONS_INDEX, AGENT_ACTIONS_RESULTS_INDEX } from '@kbn/fleet-plugin/common'; import type { BulkRequest } from '@elastic/elasticsearch/lib/api/types'; +import type { ResponseActionsApiCommandNames } from '../service/response_actions/constants'; import { EndpointError } from '../errors'; import { usageTracker } from './usage_tracker'; import type { @@ -117,6 +118,15 @@ interface BuildIEndpointAndFleetActionsBulkOperationsResponse operations: Required['operations']; } +const getAutomatedActionsSample = (): Array<{ + command: ResponseActionsApiCommandNames; + config?: { overwrite: boolean }; +}> => [ + { command: 'isolate' }, + { command: 'suspend-process', config: { overwrite: true } }, + { command: 'kill-process', config: { overwrite: true } }, +]; + export const buildIEndpointAndFleetActionsBulkOperations = ({ endpoints, count = 1, @@ -138,13 +148,14 @@ export const buildIEndpointAndFleetActionsBulkOperations = ({ for (const endpoint of endpoints) { const agentId = endpoint.elastic.agent.id; + const automatedActions = getAutomatedActionsSample(); for (let i = 0; i < count; i++) { // start with endpoint action const logsEndpointAction: LogsEndpointAction = endpointActionGenerator.generate({ EndpointActions: { data: { comment: 'data generator: this host is bad', - ...(alertIds ? { command: 'isolate' } : {}), + ...(alertIds ? automatedActions[i] : {}), }, }, }); diff --git a/x-pack/plugins/security_solution/common/endpoint/errors.ts b/x-pack/plugins/security_solution/common/endpoint/errors.ts index 495afaa126e2b..2cd7fd931583b 100644 --- a/x-pack/plugins/security_solution/common/endpoint/errors.ts +++ b/x-pack/plugins/security_solution/common/endpoint/errors.ts @@ -14,5 +14,9 @@ export class EndpointError extends Error { super(message); // For debugging - capture name of subclasses this.name = this.constructor.name; + + if (meta instanceof Error) { + this.stack += `\n----- original error -----\n${meta.stack}`; + } } } diff --git a/x-pack/plugins/security_solution/common/endpoint/service/response_actions/constants.ts b/x-pack/plugins/security_solution/common/endpoint/service/response_actions/constants.ts index 6f8daab94e186..e8de7fc1c9122 100644 --- a/x-pack/plugins/security_solution/common/endpoint/service/response_actions/constants.ts +++ b/x-pack/plugins/security_solution/common/endpoint/service/response_actions/constants.ts @@ -31,7 +31,12 @@ export const RESPONSE_ACTION_API_COMMANDS_NAMES = [ export type ResponseActionsApiCommandNames = typeof RESPONSE_ACTION_API_COMMANDS_NAMES[number]; -export const ENABLED_AUTOMATED_RESPONSE_ACTION_COMMANDS = ['isolate'] as const; +export const ENABLED_AUTOMATED_RESPONSE_ACTION_COMMANDS: ResponseActionsApiCommandNames[] = [ + 'isolate', + // TODO: TC- Uncomment these when we go GA with automated process actions + // 'kill-process', + // 'suspend-process' +]; export type EnabledAutomatedResponseActionsCommands = typeof ENABLED_AUTOMATED_RESPONSE_ACTION_COMMANDS[number]; diff --git a/x-pack/plugins/security_solution/common/experimental_features.ts b/x-pack/plugins/security_solution/common/experimental_features.ts index bed4a958b16b4..e7c5cf104b7c7 100644 --- a/x-pack/plugins/security_solution/common/experimental_features.ts +++ b/x-pack/plugins/security_solution/common/experimental_features.ts @@ -70,6 +70,11 @@ export const allowedExperimentalValues = Object.freeze({ */ responseActionUploadEnabled: true, + /* + * Enables Automated Endpoint Process actions + */ + automatedProcessActionsEnabled: false, + /** * Enables the ability to send Response actions to SentinelOne */ diff --git a/x-pack/plugins/security_solution/public/cases/attachments/external_reference.tsx b/x-pack/plugins/security_solution/public/cases/attachments/external_reference.tsx new file mode 100644 index 0000000000000..3a96cfec14897 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/attachments/external_reference.tsx @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiAvatar } from '@elastic/eui'; +import type { ExternalReferenceAttachmentType } from '@kbn/cases-plugin/public/client/attachment_framework/types'; +import { getLazyExternalChildrenContent } from './lazy_external_reference_children_content'; +import { CASE_ATTACHMENT_ENDPOINT_TYPE_ID } from '../../../common/constants'; +import { getLazyExternalEventContent } from './lazy_external_reference_content'; +import type { IExternalReferenceMetaDataProps } from './types'; + +export const getExternalReferenceAttachmentEndpointRegular = + (): ExternalReferenceAttachmentType => ({ + id: CASE_ATTACHMENT_ENDPOINT_TYPE_ID, + displayName: 'Endpoint', + // @ts-expect-error: TS2322 figure out types for children lazyExotic + getAttachmentViewObject: (props: IExternalReferenceMetaDataProps) => { + const iconType = props.externalReferenceMetadata?.command === 'isolate' ? 'lock' : 'lockOpen'; + return { + type: 'regular', + event: getLazyExternalEventContent(props), + timelineAvatar: ( + + ), + children: getLazyExternalChildrenContent, + }; + }, + }); diff --git a/x-pack/plugins/security_solution/public/cases/attachments/external_reference_children.test.tsx b/x-pack/plugins/security_solution/public/cases/attachments/external_reference_children.test.tsx new file mode 100644 index 0000000000000..e542e74a1f702 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/attachments/external_reference_children.test.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 React from 'react'; +import { render } from '@testing-library/react'; +import AttachmentContentChildren from './external_reference_children'; + +describe('AttachmentContentChildren', () => { + const defaultProps = { + command: 'isolate', + comment: 'Test comment', + targets: [ + { + endpointId: 'endpoint-1', + hostname: 'host-1', + agentType: 'endpoint' as const, + }, + ], + }; + + it('renders markdown content when comment exists', () => { + const props = { + externalReferenceMetadata: { + ...defaultProps, + }, + }; + const { getByText } = render(); + expect(getByText('Test comment')).toBeInTheDocument(); + }); + + it('does not render when comment is empty', () => { + const props = { + externalReferenceMetadata: { + ...defaultProps, + comment: '', + }, + }; + const { container } = render(); + expect(container.firstChild).toBeNull(); + }); + + it('does not render when comment is only whitespace', () => { + const props = { + externalReferenceMetadata: { + ...defaultProps, + comment: ' ', + }, + }; + + const { container } = render(); + expect(container.firstChild).toBeNull(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/cases/attachments/external_reference_children.tsx b/x-pack/plugins/security_solution/public/cases/attachments/external_reference_children.tsx new file mode 100644 index 0000000000000..57e03fd66aa39 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/attachments/external_reference_children.tsx @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import styled from 'styled-components'; +import { EuiMarkdownFormat } from '@elastic/eui'; +import type { IExternalReferenceMetaDataProps } from './types'; + +export const ContentWrapper = styled.div` + padding: ${({ theme }) => `${theme.eui?.euiSizeM} ${theme.eui?.euiSizeL}`}; + text-overflow: ellipsis; + word-break: break-word; + display: -webkit-box; + -webkit-box-orient: vertical; +`; + +const AttachmentContentChildren = ({ + externalReferenceMetadata: { comment }, +}: IExternalReferenceMetaDataProps) => { + return comment.trim().length > 0 ? ( + + {comment} + + ) : null; +}; +// eslint-disable-next-line import/no-default-export +export { AttachmentContentChildren as default }; diff --git a/x-pack/plugins/security_solution/public/cases/attachments/external_reference_event.test.tsx b/x-pack/plugins/security_solution/public/cases/attachments/external_reference_event.test.tsx new file mode 100644 index 0000000000000..4e5c504cd50c9 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/attachments/external_reference_event.test.tsx @@ -0,0 +1,88 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { render, fireEvent } from '@testing-library/react'; + +import AttachmentContentEvent from './external_reference_event'; +import { useNavigation } from '@kbn/security-solution-navigation/src/navigation'; + +jest.mock('@kbn/security-solution-navigation/src/navigation', () => { + return { + useNavigation: jest.fn(), + }; +}); + +describe('AttachmentContentEvent', () => { + const mockNavigateTo = jest.fn(); + + const mockUseNavigation = useNavigation as jest.Mocked; + (mockUseNavigation as jest.Mock).mockReturnValue({ + getAppUrl: jest.fn(), + navigateTo: mockNavigateTo, + }); + + const defaultProps = { + externalReferenceMetadata: { + command: 'isolate', + comment: 'test comment', + targets: [ + { + endpointId: 'endpoint-1', + hostname: 'host-1', + agentType: 'endpoint' as const, + }, + ], + }, + }; + + it('renders the expected text based on the command', () => { + const { getByText, getByTestId, rerender } = render( + + ); + + expect(getByText('submitted isolate request on host')).toBeInTheDocument(); + expect(getByTestId('actions-link-endpoint-1')).toHaveTextContent('host-1'); + + rerender( + + ); + + expect(getByText('submitted release request on host')).toBeInTheDocument(); + expect(getByTestId('actions-link-endpoint-1')).toHaveTextContent('host-1'); + }); + + it('navigates on link click', () => { + const { getByTestId } = render(); + + fireEvent.click(getByTestId('actions-link-endpoint-1')); + + expect(mockNavigateTo).toHaveBeenCalled(); + }); + + it('builds endpoint details URL correctly', () => { + const mockGetAppUrl = jest.fn().mockReturnValue('http://app.url'); + (mockUseNavigation as jest.Mock).mockReturnValue({ + getAppUrl: mockGetAppUrl, + }); + + render(); + + expect(mockGetAppUrl).toHaveBeenNthCalledWith(1, { + path: '/administration/endpoints?selected_endpoint=endpoint-1&show=activity_log', + }); + expect(mockGetAppUrl).toHaveBeenNthCalledWith(2, { + path: '/hosts/name/host-1', + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/cases/attachments/external_reference_event.tsx b/x-pack/plugins/security_solution/public/cases/attachments/external_reference_event.tsx new file mode 100644 index 0000000000000..1cb1910a9b1b9 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/attachments/external_reference_event.tsx @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiLink } from '@elastic/eui'; +import { useNavigation } from '@kbn/security-solution-navigation/src/navigation'; +import React, { useCallback, useMemo } from 'react'; + +import { ISOLATED_HOST, RELEASED_HOST, OTHER_ENDPOINTS } from '../pages/translations'; +import type { IExternalReferenceMetaDataProps } from './types'; +import { getEndpointDetailsPath } from '../../management/common/routing'; + +const AttachmentContentEvent = ({ + externalReferenceMetadata: { command, targets }, +}: IExternalReferenceMetaDataProps) => { + const { getAppUrl, navigateTo } = useNavigation(); + + const endpointDetailsHref = getAppUrl({ + path: getEndpointDetailsPath({ + name: 'endpointActivityLog', + selected_endpoint: targets[0].endpointId, + }), + }); + const hostsDetailsHref = getAppUrl({ + path: `/hosts/name/${targets[0].hostname}`, + }); + + const actionText = useMemo(() => { + return command === 'isolate' ? `${ISOLATED_HOST} ` : `${RELEASED_HOST} `; + }, [command]); + + const linkHref = useMemo( + () => (targets[0].agentType === 'endpoint' ? endpointDetailsHref : hostsDetailsHref), + [endpointDetailsHref, hostsDetailsHref, targets] + ); + + const onLinkClick = useCallback( + (ev: React.MouseEvent) => { + ev.preventDefault(); + return navigateTo({ url: linkHref }); + }, + [navigateTo, linkHref] + ); + + return ( + <> + {actionText} + {/* eslint-disable-next-line @elastic/eui/href-or-on-click */} + + {targets[0].hostname} + + {targets.length > 1 && OTHER_ENDPOINTS(targets.length - 1)} + + ); +}; + +// eslint-disable-next-line import/no-default-export +export { AttachmentContentEvent as default }; diff --git a/x-pack/plugins/security_solution/public/cases/attachments/lazy_external_reference_children_content.tsx b/x-pack/plugins/security_solution/public/cases/attachments/lazy_external_reference_children_content.tsx new file mode 100644 index 0000000000000..b88eeddf2b6bc --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/attachments/lazy_external_reference_children_content.tsx @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { lazy, Suspense } from 'react'; +import type { IExternalReferenceMetaDataProps } from './types'; + +const AttachmentContent = lazy(() => import('./external_reference_children')); + +export const getLazyExternalChildrenContent = (props: IExternalReferenceMetaDataProps) => { + return ( + + + + ); +}; diff --git a/x-pack/plugins/security_solution/public/cases/attachments/lazy_external_reference_content.tsx b/x-pack/plugins/security_solution/public/cases/attachments/lazy_external_reference_content.tsx new file mode 100644 index 0000000000000..cbcf2dade0a8e --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/attachments/lazy_external_reference_content.tsx @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { lazy, Suspense } from 'react'; +import type { IExternalReferenceMetaDataProps } from './types'; + +const AttachmentContent = lazy(() => import('./external_reference_event')); + +export const getLazyExternalEventContent = (props: IExternalReferenceMetaDataProps) => { + return ( + + + + ); +}; diff --git a/x-pack/plugins/security_solution/public/cases/attachments/types.ts b/x-pack/plugins/security_solution/public/cases/attachments/types.ts new file mode 100644 index 0000000000000..341b21a7576e4 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/attachments/types.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ResponseActionAgentType } from '../../../common/endpoint/service/response_actions/constants'; + +export interface IExternalReferenceMetaDataProps { + externalReferenceMetadata: { + comment: ExternalReferenceCommentType; + command: ExternalReferenceCommandType; + targets: ExternalReferenceTargetsType; + }; +} + +type ExternalReferenceTargetsType = Array<{ + endpointId: string; + hostname: string; + agentType: ResponseActionAgentType; +}>; +type ExternalReferenceCommentType = string; +type ExternalReferenceCommandType = string; diff --git a/x-pack/plugins/security_solution/public/cases/pages/translations.ts b/x-pack/plugins/security_solution/public/cases/pages/translations.ts index d6f33d2a90ffa..776b7acc11898 100644 --- a/x-pack/plugins/security_solution/public/cases/pages/translations.ts +++ b/x-pack/plugins/security_solution/public/cases/pages/translations.ts @@ -10,3 +10,17 @@ import { i18n } from '@kbn/i18n'; export const PAGE_TITLE = i18n.translate('xpack.securitySolution.cases.pageTitle', { defaultMessage: 'Cases', }); + +export const ISOLATED_HOST = i18n.translate('xpack.securitySolution.caseView.isolatedHost', { + defaultMessage: 'submitted isolate request on host', +}); + +export const RELEASED_HOST = i18n.translate('xpack.securitySolution.caseView.releasedHost', { + defaultMessage: 'submitted release request on host', +}); + +export const OTHER_ENDPOINTS = (endpoints: number): string => + i18n.translate('xpack.securitySolution.caseView.otherEndpoints', { + values: { endpoints }, + defaultMessage: ` and {endpoints} {endpoints, plural, =1 {other} other {others}}`, + }); diff --git a/x-pack/plugins/security_solution/public/common/translations.ts b/x-pack/plugins/security_solution/public/common/translations.ts index 551e977ec9861..676546b63b224 100644 --- a/x-pack/plugins/security_solution/public/common/translations.ts +++ b/x-pack/plugins/security_solution/public/common/translations.ts @@ -77,7 +77,7 @@ export const UNSAVED_TIMELINE_SAVE_PROMPT_TITLE = i18n.translate( export const getAgentTypeName = (agentType: ResponseActionAgentType) => { switch (agentType) { case 'endpoint': - return 'Endpoint'; + return 'Elastic Defend'; case 'sentinel_one': return 'SentinelOne'; default: diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/endpoint/action_type_field.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/endpoint/action_type_field.tsx index fa40754f222c4..60857e4b5c49a 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/endpoint/action_type_field.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/endpoint/action_type_field.tsx @@ -13,6 +13,7 @@ import { SuperSelectField } from '@kbn/es-ui-shared-plugin/static/forms/componen import { fieldValidators } from '@kbn/es-ui-shared-plugin/static/forms/helpers'; import { FormattedMessage } from '@kbn/i18n-react'; import { EuiLink } from '@elastic/eui'; +import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; import { getRbacControl } from '../../../../common/endpoint/service/response_actions/utils'; import { useKibana } from '../../../common/lib/kibana'; import { CHOOSE_FROM_THE_LIST, LEARN_MORE } from './translations'; @@ -44,14 +45,29 @@ const ActionTypeFieldComponent = ({ }, } = useKibana().services; + const automatedProcessActionsEnabled = useIsExperimentalFeatureEnabled( + 'automatedProcessActionsEnabled' + ); + + const enabledActions = useMemo( + () => + [ + ...ENABLED_AUTOMATED_RESPONSE_ACTION_COMMANDS, + ...(automatedProcessActionsEnabled ? ['kill-process', 'suspend-process'] : []), + ] as ['isolate', 'kill-process', 'suspend-process'], + [automatedProcessActionsEnabled] + ); + const fieldOptions = useMemo( () => - ENABLED_AUTOMATED_RESPONSE_ACTION_COMMANDS.map((name) => { + enabledActions.map((name) => { const missingRbac = !getRbacControl({ commandName: RESPONSE_ACTION_API_COMMAND_TO_CONSOLE_COMMAND_MAP[name], privileges: endpointPrivileges, }); - const commandAlreadyExists = map(data.responseActions, 'params.command').includes(name); + const currentActions = map(data.responseActions, 'params.command'); + // we enable just one instance of each action + const commandAlreadyExists = currentActions.includes(name); const isDisabled = commandAlreadyExists || missingRbac; return { @@ -62,7 +78,7 @@ const ActionTypeFieldComponent = ({ 'data-test-subj': `command-type-${name}`, }; }), - [data.responseActions, endpointPrivileges] + [data.responseActions, enabledActions, endpointPrivileges] ); return ( diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/endpoint/callout.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/endpoint/callout.tsx index 07dbb0c9f2cbf..3468743f5d9d7 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/endpoint/callout.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/endpoint/callout.tsx @@ -71,6 +71,31 @@ const EndpointActionCalloutComponent = ({ basePath, editDisabled }: EndpointCall ); } + if (currentCommand === 'kill-process' || currentCommand === 'suspend-process') { + return ( + <> + + + } + > + + + + + + + ); + } return <>; }; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/endpoint/config_fields.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/endpoint/config_fields.tsx new file mode 100644 index 0000000000000..e59fb52425d94 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/endpoint/config_fields.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { useFormData } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; +import { EuiSpacer } from '@elastic/eui'; +import { get } from 'lodash'; +import { OverwriteField } from './overwrite_process_field'; +import { FieldNameField } from './field_name'; + +interface AdditionalConfigFieldProps { + basePath: string; + disabled: boolean; + readDefaultValueOnForm: boolean; +} + +export const ConfigFieldsComponent = ({ + basePath, + disabled, + readDefaultValueOnForm, +}: AdditionalConfigFieldProps) => { + const commandPath = `${basePath}.command`; + const overWritePath = `${basePath}.config.overwrite`; + const [data] = useFormData({ watch: [commandPath, overWritePath] }); + const currentCommand = get(data, commandPath); + const currentOverwrite = get(data, overWritePath); + + if (currentCommand === 'kill-process' || currentCommand === 'suspend-process') { + return ( + <> + + + + + + + ); + } + + return null; +}; + +export const ConfigFields = React.memo(ConfigFieldsComponent); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/endpoint/endpoint_response_action.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/endpoint/endpoint_response_action.tsx index 940cab8eb1b8b..84518ac097481 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/endpoint/endpoint_response_action.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/endpoint/endpoint_response_action.tsx @@ -6,6 +6,7 @@ */ import React from 'react'; +import { ConfigFields } from './config_fields'; import type { ArrayItem } from '../../../shared_imports'; import { CommentField } from './comment_field'; import { ActionTypeField } from './action_type_field'; @@ -29,6 +30,12 @@ export const EndpointResponseAction = React.memo((props: EndpointResponseActionP + + ({ + label: ecs.field, + value: ecs, +})); + +const SINGLE_SELECTION = Object.freeze({ asPlainText: true }); + +const FIELD_LABEL: string = 'Custom field name'; +const FieldNameFieldComponent = ({ + path, + disabled, + readDefaultValueOnForm, + isRequired, +}: FieldNameFieldProps) => { + const [data] = useFormData(); + const fieldValue = get(data, path); + const context = useFormContext(); + + const currentFieldNameField = context.getFields()[path]; + + useEffect(() => { + // hackish way to clear errors on this field - because we base this validation on the value of overwrite toggle + if (currentFieldNameField && !isRequired) { + currentFieldNameField?.clearErrors(); + } + }, [currentFieldNameField, isRequired]); + + const renderEntityIdNote = useMemo(() => { + const contains = fieldValue?.includes('entity_id'); + if (contains) { + return ( + + ); + } + return null; + }, [fieldValue]); + + const CONFIG = useMemo(() => { + return { + label: FIELD_LABEL, + helpText: renderEntityIdNote, + validations: [ + { + validator: ({ value }: { value: string }) => { + if (isRequired && value === '') { + return { + code: 'ERR_FIELD_MISSING', + path, + message: i18n.translate( + 'xpack.securitySolution.responseActions.endpoint.validations.fieldNameIsRequiredErrorMessage', + { + defaultMessage: + '{field} is a required field when process.pid toggle is turned off', + values: { field: FIELD_LABEL }, + } + ), + }; + } + }, + }, + ], + }; + }, [isRequired, path, renderEntityIdNote]); + + const optionsAsComboBoxOptions = useMemo(() => { + return ECSSchemaOptions.map(({ label }) => ({ + label, + value: label, + })); + }, []); + + return ( + <> + path={path} readDefaultValueOnForm={readDefaultValueOnForm} config={CONFIG}> + {(field) => { + const { value, setValue } = field; + const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); + + const valueInList = !!optionsAsComboBoxOptions.find((option) => option.label === value); + return ( + + { + if (newValue.length === 0) { + // Don't allow clearing the type. One must always be selected + return; + } + setValue(newValue[0].label); + }} + data-test-subj="config-custom-field-name" + /> + + ); + }} + + + + ); +}; +export const FieldNameField = React.memo(FieldNameFieldComponent); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/endpoint/overwrite_process_field.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/endpoint/overwrite_process_field.tsx new file mode 100644 index 0000000000000..143bd78e339d7 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/endpoint/overwrite_process_field.tsx @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React, { useMemo } from 'react'; +import { UseField } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; +import { ToggleField } from '@kbn/es-ui-shared-plugin/static/forms/components'; +import { i18n } from '@kbn/i18n'; + +interface OverwriteFieldProps { + path: string; + disabled: boolean; + readDefaultValueOnForm: boolean; +} + +const OverwriteFieldComponent = ({ + path, + disabled, + readDefaultValueOnForm, +}: OverwriteFieldProps) => { + const CONFIG = useMemo(() => { + return { + defaultValue: true, + label: i18n.translate('xpack.securitySolution.responseActions.endpoint.overwriteFieldLabel', { + defaultMessage: 'Use process.pid as process identifier', + }), + }; + }, []); + + return ( + + ); +}; + +export const OverwriteField = React.memo(OverwriteFieldComponent); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/endpoint/utils.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/endpoint/utils.tsx index 58f8dd2e3548e..a07fd84bf4430 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/endpoint/utils.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/endpoint/utils.tsx @@ -6,7 +6,7 @@ */ import type { ReactNode } from 'react'; import React from 'react'; -import { EuiText, EuiTitle, EuiSpacer, EuiToolTip } from '@elastic/eui'; +import { EuiText, EuiSpacer, EuiToolTip } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import type { EnabledAutomatedResponseActionsCommands } from '../../../../common/endpoint/service/response_actions/constants'; @@ -20,11 +20,11 @@ const EndpointActionTextComponent = ({ name, isDisabled }: EndpointActionTextPro const content = ( <> - - {title} - + + {title} + - {description} + {description} ); if (isDisabled) { @@ -62,6 +62,48 @@ const useGetCommandText = ( /> ), }; + case 'kill-process': + return { + title: ( + + ), + description: ( + + ), + tooltip: ( + + ), + }; + case 'suspend-process': + return { + title: ( + + ), + description: ( + + ), + tooltip: ( + + ), + }; default: return { title: '', diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/endpoint/v.8.10.0_process.json b/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/endpoint/v.8.10.0_process.json new file mode 100644 index 0000000000000..986a60ba6d48f --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/endpoint/v.8.10.0_process.json @@ -0,0 +1,122 @@ +[ + { + "field": "process.entity_id", + "description": "Unique identifier for the process." + }, + { + "field": "process.entry_leader.entity_id", + "description": "Unique identifier for the process." + }, + { + "field": "process.entry_leader.parent.entity_id", + "description": "Unique identifier for the process." + }, + { + "field": "process.entry_leader.parent.pid", + "description": "Process id." + }, + { + "field": "process.entry_leader.parent.session_leader.entity_id", + "description": "Unique identifier for the process." + }, + { + "field": "process.entry_leader.parent.session_leader.pid", + "description": "Process id." + }, + { + "field": "process.entry_leader.parent.session_leader.vpid", + "description": "Virtual process id." + }, + { + "field": "process.entry_leader.parent.vpid", + "description": "Virtual process id." + }, + { + "field": "process.entry_leader.pid", + "description": "Process id." + }, + { + "field": "process.entry_leader.vpid", + "description": "Virtual process id." + }, + { + "field": "process.group_leader.entity_id", + "description": "Unique identifier for the process." + }, + { + "field": "process.group_leader.pid", + "description": "Process id." + }, + { + "field": "process.group_leader.vpid", + "description": "Virtual process id." + }, + { + "field": "process.parent.entity_id", + "description": "Unique identifier for the process." + }, + { + "field": "process.parent.group_leader.entity_id", + "description": "Unique identifier for the process." + }, + { + "field": "process.parent.group_leader.pid", + "description": "Process id." + }, + { + "field": "process.parent.group_leader.vpid", + "description": "Virtual process id." + }, + { + "field": "process.parent.pid", + "description": "Process id." + }, + { + "field": "process.parent.vpid", + "description": "Virtual process id." + }, + { + "field": "process.pid", + "description": "Process id." + }, + { + "field": "process.session_leader.entity_id", + "description": "Unique identifier for the process." + }, + { + "field": "process.session_leader.parent.entity_id", + "description": "Unique identifier for the process." + }, + { + "field": "process.session_leader.parent.pid", + "description": "Process id." + }, + { + "field": "process.session_leader.parent.session_leader.entity_id", + "description": "Unique identifier for the process." + }, + { + "field": "process.session_leader.parent.session_leader.pid", + "description": "Process id." + }, + { + "field": "process.session_leader.parent.session_leader.vpid", + "description": "Virtual process id." + }, + { + "field": "process.session_leader.parent.vpid", + "description": "Virtual process id." + }, + { + "field": "process.session_leader.pid", + "description": "Process id." + }, + { + "field": "process.session_leader.vpid", + "description": "Virtual process id." + }, + { + "field": "process.vpid", + "description": "Virtual process id." + } +] diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/response_actions_form.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/response_actions_form.tsx index cefff7db70467..31b5f16c1a037 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/response_actions_form.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/response_actions_form.tsx @@ -53,14 +53,24 @@ export const ResponseActionsForm = ({ const fieldErrors = reduce>( map(items, 'path'), (acc, path) => { - if (fields[`${path}.params`]?.errors?.length) { - acc.push({ - type: upperFirst((fields[`${path}.actionTypeId`].value as string).substring(1)), - errors: map(fields[`${path}.params`].errors, 'message'), - }); - return acc; - } + map(fields, (_, name) => { + const paramsPath = `${path}.params`; + + if (name.includes(paramsPath)) { + if (fields[name]?.errors?.length) { + const responseActionType = upperFirst( + (fields[`${path}.actionTypeId`].value as string).substring(1) + ); + acc.push({ + type: responseActionType, + errors: map(fields[name].errors, 'message'), + }); + } + return acc; + } + return acc; + }); 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 1d1346f9090c2..aed3d0302f05c 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 @@ -17,8 +17,8 @@ export const useSupportedResponseActionTypes = () => { >(); const isEndpointEnabled = useIsExperimentalFeatureEnabled('endpointResponseActionsEnabled'); - const { canIsolateHost } = useUserPrivileges().endpointPrivileges; - + const { canIsolateHost, canKillProcess, canSuspendProcess } = + useUserPrivileges().endpointPrivileges; const enabledFeatures = useMemo( () => ({ endpoint: isEndpointEnabled, @@ -28,9 +28,9 @@ export const useSupportedResponseActionTypes = () => { const userHasPermissionsToExecute = useMemo( () => ({ - endpoint: canIsolateHost, + endpoint: canIsolateHost || canKillProcess || canSuspendProcess, }), - [canIsolateHost] + [canIsolateHost, canKillProcess, canSuspendProcess] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_grouping.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_grouping.test.tsx index b1a838ba74598..1153cced85ba9 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_grouping.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_grouping.test.tsx @@ -529,4 +529,41 @@ describe('GroupedAlertsTable', () => { } }); }); + + it('sends telemetry data when selected group changes', () => { + jest + .spyOn(window.localStorage, 'getItem') + .mockReturnValue(getMockStorageState(['kibana.alert.rule.name'])); + store = createMockStore({ + ...mockGlobalState, + groups: { + [testProps.tableId]: { + options: mockOptions, + activeGroups: ['kibana.alert.rule.name'], + }, + }, + }); + + const { getByTestId } = render( + + + + ); + + fireEvent.click(getByTestId('group-selector-dropdown')); + fireEvent.click(getByTestId('panel-user.name')); + + expect(mockedTelemetry.reportAlertsGroupingChanged).toHaveBeenCalledWith({ + groupByField: 'user.name', + tableId: testProps.tableId, + }); + + fireEvent.click(getByTestId('group-selector-dropdown')); + fireEvent.click(getByTestId('panel-host.name')); + + expect(mockedTelemetry.reportAlertsGroupingChanged).toHaveBeenCalledWith({ + groupByField: 'host.name', + tableId: testProps.tableId, + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_grouping.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_grouping.tsx index f48a288dad64c..3382a94d6c70c 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_grouping.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_grouping.tsx @@ -76,8 +76,8 @@ const GroupedAlertsTableComponent: React.FC = (props) const { onGroupChange, onGroupToggle } = useMemo( () => ({ - onGroupChange: (param: { groupByField: string; tableId: string }) => { - telemetry.reportAlertsGroupingChanged(param); + onGroupChange: ({ groupByField, tableId }: { groupByField: string; tableId: string }) => { + telemetry.reportAlertsGroupingChanged({ groupByField, tableId }); }, onGroupToggle: (param: { isOpen: boolean; diff --git a/x-pack/plugins/security_solution/public/detections/components/endpoint_responder/use_responder_action_data.ts b/x-pack/plugins/security_solution/public/detections/components/endpoint_responder/use_responder_action_data.ts index 8a4d98ae76f90..62772fa029e66 100644 --- a/x-pack/plugins/security_solution/public/detections/components/endpoint_responder/use_responder_action_data.ts +++ b/x-pack/plugins/security_solution/public/detections/components/endpoint_responder/use_responder_action_data.ts @@ -39,7 +39,7 @@ const getThirdPartyAgentInfo = ( ) as ResponseActionAgentType, }, host: { - name: getFieldValue({ category: 'host', field: 'host.os.name' }, eventData), + name: getFieldValue({ category: 'host', field: 'host.name' }, eventData), os: { name: getFieldValue({ category: 'host', field: 'host.os.name' }, eventData), family: getFieldValue({ category: 'host', field: 'host.os.family' }, eventData), diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/mitre_attack.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/mitre_attack.test.tsx index 4768be2ad278a..41a7e7b0e3da9 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/mitre_attack.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/mitre_attack.test.tsx @@ -19,7 +19,8 @@ const renderMitreAttack = (contextValue: RightPanelContext) => ); -describe('', () => { +// FLAKY: https://github.com/elastic/kibana/issues/176002 +describe.skip('', () => { it('should render mitre attack information', async () => { const contextValue = { searchHit: mockSearchHit } as unknown as RightPanelContext; diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/action_log_expanded_tray.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/action_log_expanded_tray.tsx index 03268178bc3d9..5a44f6584520a 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/action_log_expanded_tray.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/action_log_expanded_tray.tsx @@ -9,6 +9,8 @@ import React, { memo, useMemo } from 'react'; import { EuiCodeBlock, EuiDescriptionList, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { css, euiStyled } from '@kbn/kibana-react-plugin/common'; import { map } from 'lodash'; +import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; +import { getAgentTypeName } from '../../../../common/translations'; import { RESPONSE_ACTION_API_COMMAND_TO_CONSOLE_COMMAND_MAP } from '../../../../../common/endpoint/service/response_actions/constants'; import { isExecuteAction, @@ -178,7 +180,19 @@ export const ActionsLogExpandedTray = memo<{ }>(({ action, 'data-test-subj': dataTestSubj }) => { const getTestId = useTestIdGenerator(dataTestSubj); - const { hosts, startedAt, completedAt, command: _command, comment, parameters } = action; + const isSentinelOneV1Enabled = useIsExperimentalFeatureEnabled( + 'responseActionsSentinelOneV1Enabled' + ); + + const { + hosts, + startedAt, + completedAt, + command: _command, + comment, + parameters, + agentType, + } = action; const parametersList = useMemo( () => @@ -192,45 +206,61 @@ export const ActionsLogExpandedTray = memo<{ const command = RESPONSE_ACTION_API_COMMAND_TO_CONSOLE_COMMAND_MAP[_command]; - const dataList = useMemo( - () => - [ - { - title: OUTPUT_MESSAGES.expandSection.placedAt, - description: `${startedAt}`, - }, - { - title: OUTPUT_MESSAGES.expandSection.startedAt, - description: `${startedAt}`, - }, - { - title: OUTPUT_MESSAGES.expandSection.completedAt, - description: `${completedAt ?? emptyValue}`, - }, - { - title: OUTPUT_MESSAGES.expandSection.input, - description: `${command}`, - }, - { - title: OUTPUT_MESSAGES.expandSection.parameters, - description: parametersList ? parametersList.join(', ') : emptyValue, - }, - { - title: OUTPUT_MESSAGES.expandSection.comment, - description: comment ? comment : emptyValue, - }, - { - title: OUTPUT_MESSAGES.expandSection.hostname, - description: map(hosts, (host) => host.name).join(', ') || emptyValue, - }, - ].map(({ title, description }) => { - return { - title: {title}, - description: {description}, - }; - }), - [command, comment, completedAt, hosts, parametersList, startedAt] - ); + const dataList = useMemo(() => { + const list = [ + { + title: OUTPUT_MESSAGES.expandSection.placedAt, + description: `${startedAt}`, + }, + { + title: OUTPUT_MESSAGES.expandSection.startedAt, + description: `${startedAt}`, + }, + { + title: OUTPUT_MESSAGES.expandSection.completedAt, + description: `${completedAt ?? emptyValue}`, + }, + { + title: OUTPUT_MESSAGES.expandSection.input, + description: `${command}`, + }, + { + title: OUTPUT_MESSAGES.expandSection.parameters, + description: parametersList ? parametersList.join(', ') : emptyValue, + }, + { + title: OUTPUT_MESSAGES.expandSection.comment, + description: comment ? comment : emptyValue, + }, + { + title: OUTPUT_MESSAGES.expandSection.hostname, + description: map(hosts, (host) => host.name).join(', ') || emptyValue, + }, + ]; + + if (isSentinelOneV1Enabled) { + list.push({ + title: OUTPUT_MESSAGES.expandSection.agentType, + description: getAgentTypeName(agentType) || emptyValue, + }); + } + + return list.map(({ title, description }) => { + return { + title: {title}, + description: {description}, + }; + }); + }, [ + agentType, + command, + comment, + completedAt, + hosts, + isSentinelOneV1Enabled, + parametersList, + startedAt, + ]); const outputList = useMemo( () => [ diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/hooks.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/hooks.tsx index 36d5d8d1f556b..b8e08f557aa2d 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/hooks.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/hooks.tsx @@ -189,18 +189,28 @@ const getTypesFilterInitialState = ( // v8.13 onwards // for showing agent types and action types in the same filter if (isSentinelOneV1Enabled) { + if (!isFlyout) { + return [ + { + label: FILTER_NAMES.agentTypes, + isGroupLabel: true, + }, + ...RESPONSE_ACTION_AGENT_TYPE.map((type) => + getFilterOptions({ + key: type, + label: getAgentTypeName(type), + checked: !isFlyout && agentTypes?.includes(type) ? 'on' : undefined, + }) + ), + { + label: FILTER_NAMES.actionTypes, + isGroupLabel: true, + }, + ...defaultFilterOptions, + ]; + } + return [ - { - label: FILTER_NAMES.agentTypes, - isGroupLabel: true, - }, - ...RESPONSE_ACTION_AGENT_TYPE.map((type) => - getFilterOptions({ - key: type, - label: getAgentTypeName(type), - checked: !isFlyout && agentTypes?.includes(type) ? 'on' : undefined, - }) - ), { label: FILTER_NAMES.actionTypes, isGroupLabel: true, @@ -336,7 +346,10 @@ export const useActionsLogFilter = ({ () => items.filter((item) => item.checked === 'on').length, [items] ); - const numFilters = useMemo(() => items.filter((item) => item.checked !== 'on').length, [items]); + const numFilters = useMemo( + () => items.filter((item) => item.key && item.checked !== 'on').length, + [items] + ); return { areHostsSelectedOnMount, 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 2b909e637bd20..3486da1191b4c 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 @@ -28,8 +28,10 @@ import { getActionListMock } from '../mocks'; import { useGetEndpointsList } from '../../../hooks/endpoint/use_get_endpoints_list'; import { v4 as uuidv4 } from 'uuid'; import { + RESPONSE_ACTION_AGENT_TYPE, RESPONSE_ACTION_API_COMMAND_TO_CONSOLE_COMMAND_MAP, RESPONSE_ACTION_API_COMMANDS_NAMES, + RESPONSE_ACTION_TYPE, } from '../../../../../common/endpoint/service/response_actions/constants'; import { useUserPrivileges as _useUserPrivileges } from '../../../../common/components/user_privileges'; import { responseActionsHttpMocks } from '../../../mocks/response_actions_http_mocks'; @@ -571,6 +573,30 @@ describe('Response actions history', () => { ); }); + it('should contain agent type info in each expanded row', async () => { + mockedContext.setExperimentalFlag({ responseActionsSentinelOneV1Enabled: true }); + render(); + const { getAllByTestId } = renderResult; + + const expandButtons = getAllByTestId(`${testPrefix}-expand-button`); + expandButtons.map((button) => userEvent.click(button)); + const trays = getAllByTestId(`${testPrefix}-details-tray`); + expect(trays).toBeTruthy(); + expect(Array.from(trays[0].querySelectorAll('dt')).map((title) => title.textContent)).toEqual( + [ + 'Command placed', + 'Execution started on', + 'Execution completed', + 'Input', + 'Parameters', + 'Comment', + 'Hostname', + 'Agent type', + 'Output:', + ] + ); + }); + it('should refresh data when autoRefresh is toggled on', async () => { const listHookResponse = getBaseMockedActionList(); useGetEndpointActionListMock.mockReturnValue(listHookResponse); @@ -1380,4 +1406,49 @@ describe('Response actions history', () => { ); }); }); + + describe('Types filter', () => { + const filterPrefix = 'types-filter'; + it('should show a list of action types when opened', () => { + render(); + const { getByTestId, getAllByTestId } = renderResult; + + userEvent.click(getByTestId(`${testPrefix}-${filterPrefix}-popoverButton`)); + const filterList = getByTestId(`${testPrefix}-${filterPrefix}-popoverList`); + expect(filterList).toBeTruthy(); + expect(getAllByTestId(`${filterPrefix}-option`).length).toEqual(RESPONSE_ACTION_TYPE.length); + expect(getAllByTestId(`${filterPrefix}-option`).map((option) => option.textContent)).toEqual([ + 'Triggered by rule', + 'Triggered manually', + ]); + }); + + it('should show a list of agent and action types when opened in page view', () => { + mockedContext.setExperimentalFlag({ responseActionsSentinelOneV1Enabled: true }); + render({ isFlyout: false }); + const { getByTestId, getAllByTestId } = renderResult; + + userEvent.click(getByTestId(`${testPrefix}-${filterPrefix}-popoverButton`)); + const filterList = getByTestId(`${testPrefix}-${filterPrefix}-popoverList`); + expect(filterList).toBeTruthy(); + expect(getAllByTestId(`${filterPrefix}-option`).length).toEqual( + [...RESPONSE_ACTION_AGENT_TYPE, ...RESPONSE_ACTION_TYPE].length + ); + expect(getAllByTestId(`${filterPrefix}-option`).map((option) => option.textContent)).toEqual([ + 'Elastic Defend', + 'SentinelOne', + 'Triggered by rule', + 'Triggered manually', + ]); + }); + + it('should have `clear all` button `disabled` when no selected values', () => { + render(); + const { getByTestId } = renderResult; + + userEvent.click(getByTestId(`${testPrefix}-${filterPrefix}-popoverButton`)); + const clearAllButton = getByTestId(`${testPrefix}-${filterPrefix}-clearAllButton`); + expect(clearAllButton.hasAttribute('disabled')).toBeTruthy(); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/translations.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/translations.tsx index 907348790e92d..b11f31bbd1b72 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/translations.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/translations.tsx @@ -77,6 +77,12 @@ export const OUTPUT_MESSAGES = Object.freeze({ defaultMessage: 'Hostname', } ), + agentType: i18n.translate( + 'xpack.securitySolution.responseActionsList.list.item.expandSection.agentType', + { + defaultMessage: 'Agent type', + } + ), }, }); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/automated_response_actions/automated_response_actions.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/automated_response_actions/automated_response_actions.cy.ts index 1948434b39c9f..38261547f8d27 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/automated_response_actions/automated_response_actions.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/automated_response_actions/automated_response_actions.cy.ts @@ -11,7 +11,7 @@ import { closeAllToasts } from '../../tasks/toasts'; import { toggleRuleOffAndOn, visitRuleAlerts } from '../../tasks/isolate'; import { cleanupRule, loadRule } from '../../tasks/api_fixtures'; import { login } from '../../tasks/login'; -import { disableExpandableFlyoutAdvancedSettings, loadPage } from '../../tasks/common'; +import { loadPage } from '../../tasks/common'; import type { IndexedFleetEndpointPolicyResponse } from '../../../../../common/endpoint/data_loaders/index_fleet_endpoint_policy'; import { createAgentPolicyTask, getEndpointIntegrationVersion } from '../../tasks/fleet'; import { changeAlertsFilter } from '../../tasks/alerts'; @@ -23,14 +23,16 @@ import { enableAllPolicyProtections } from '../../tasks/endpoint_policy'; describe( 'Automated Response Actions', { - tags: [ - '@ess', - '@serverless', - // Not supported in serverless! - // The `disableExpandableFlyoutAdvancedSettings()` fails because the API - // `internal/kibana/settings` is not accessible in serverless - '@brokenInServerless', - ], + tags: ['@ess', '@serverless'], + env: { + ftrConfig: { + kbnServerArgs: [ + `--xpack.securitySolution.enableExperimental=${JSON.stringify([ + 'automatedProcessActionsEnabled', + ])}`, + ], + }, + }, }, () => { let indexedPolicy: IndexedFleetEndpointPolicyResponse; @@ -67,16 +69,11 @@ describe( } }); - const hostname = new URL(Cypress.env('FLEET_SERVER_URL')).port; - const fleetHostname = `dev-fleet-server.${hostname}`; - beforeEach(() => { login(); - disableExpandableFlyoutAdvancedSettings(); }); - // FLAKY: https://github.com/elastic/kibana/issues/169828 - describe.skip('From alerts', () => { + describe('From alerts', () => { let ruleId: string; let ruleName: string; @@ -102,18 +99,14 @@ describe( visitRuleAlerts(ruleName); closeAllToasts(); - changeAlertsFilter('event.category: "file"'); - cy.getByTestSubj('expand-event').first().click(); - cy.getByTestSubj('responseActionsViewTab').click(); - cy.getByTestSubj('response-actions-notification').should('not.have.text', '0'); - - cy.getByTestSubj(`response-results-${createdHost.hostname}-details-tray`) - .should('contain', 'isolate completed successfully') - .and('contain', createdHost.hostname); + changeAlertsFilter('process.name: "sshd"'); + cy.getByTestSubj('expand-event').eq(0).click(); + cy.getByTestSubj('securitySolutionFlyoutNavigationExpandDetailButton').click(); + cy.getByTestSubj('securitySolutionFlyoutResponseTab').click(); - cy.getByTestSubj(`response-results-${fleetHostname}-details-tray`) - .should('contain', 'The host does not have Elastic Defend integration installed') - .and('contain', 'dev-fleet-server'); + cy.contains(/isolate is pending|isolate completed successfully/g); + cy.contains(/kill-process is pending|kill-process completed successfully/g); + cy.contains('The action was called with a non-existing event field name: entity_id'); }); }); } diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/automated_response_actions/form.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/automated_response_actions/form.cy.ts index 27458ef09e15b..4fa0bb3b0c2e0 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/automated_response_actions/form.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/automated_response_actions/form.cy.ts @@ -17,22 +17,26 @@ import { cleanupRule, generateRandomStringName, loadRule } from '../../tasks/api import { ResponseActionTypesEnum } from '../../../../../common/api/detection_engine'; import { login, ROLE } from '../../tasks/login'; +export const RESPONSE_ACTIONS_ERRORS = 'response-actions-error'; + describe( 'Form', { - tags: [ - '@ess', - '@serverless', - - // Not supported in serverless! Test suite uses custom roles - '@brokenInServerless', - ], + tags: ['@ess', '@serverless'], + env: { + ftrConfig: { + kbnServerArgs: [ + `--xpack.securitySolution.enableExperimental=${JSON.stringify([ + 'automatedProcessActionsEnabled', + ])}`, + ], + }, + }, }, () => { - // FLAKY: https://github.com/elastic/kibana/issues/169334 - describe.skip('User with no access can not create an endpoint response action', () => { + describe('User with no access can not create an endpoint response action', () => { beforeEach(() => { - login(ROLE.endpoint_response_actions_no_access); + login(ROLE.rule_author); }); it('no endpoint response action option during rule creation', () => { @@ -43,11 +47,12 @@ describe( describe('User with access can create and save an endpoint response action', () => { const testedCommand = 'isolate'; + const secondTestedCommand = 'suspend-process'; let ruleId: string; const [ruleName, ruleDescription] = generateRandomStringName(2); beforeEach(() => { - login(ROLE.endpoint_response_actions_access); + login(ROLE.soc_manager); }); afterEach(() => { if (ruleId) { @@ -75,20 +80,53 @@ describe( }); cy.getByTestSubj(`command-type-${testedCommand}`).should('not.have.attr', 'disabled'); cy.getByTestSubj(`command-type-${testedCommand}`).click(); + + addEndpointResponseAction(); + focusAndOpenCommandDropdown(1); + cy.getByTestSubj(`command-type-${secondTestedCommand}`).click(); + cy.getByTestSubj('config-overwrite-toggle').click(); + cy.getByTestSubj('config-custom-field-name').should('have.value', ''); + cy.intercept('POST', '/api/detection_engine/rules', (request) => { - const result = { + const isolateResult = { action_type_id: ResponseActionTypesEnum['.endpoint'], params: { command: testedCommand, comment: 'example1', }, }; - expect(request.body.response_actions[0]).to.deep.equal(result); + const processResult = { + action_type_id: ResponseActionTypesEnum['.endpoint'], + params: { + command: secondTestedCommand, + comment: 'example1', + config: { + field: 'process.entity_id', + overwrite: false, + }, + }, + }; + expect(request.body.response_actions[0]).to.deep.equal(isolateResult); + expect(request.body.response_actions[1]).to.deep.equal(processResult); request.continue((response) => { ruleId = response.body.id; response.send(response.body); }); }); + cy.getByTestSubj(RESPONSE_ACTIONS_ERRORS).should('not.exist'); + + cy.getByTestSubj('create-enabled-false').click(); + + cy.getByTestSubj(RESPONSE_ACTIONS_ERRORS).within(() => { + cy.contains( + 'Custom field name is a required field when process.pid toggle is turned off' + ); + }); + + cy.getByTestSubj(`response-actions-list-item-1`).within(() => { + cy.getByTestSubj('config-custom-field-name').type('process.entity_id{downArrow}{enter}'); + }); + cy.getByTestSubj('create-enabled-false').click(); cy.contains(`${ruleName} was created`); }); @@ -97,11 +135,11 @@ describe( describe('User with access can edit and delete an endpoint response action', () => { let ruleId: string; let ruleName: string; - const testedCommand = 'isolate'; - const newDescription = 'Example isolate host description'; + const newDescription = 'Example suspend process description'; + const secondTestedCommand = 'suspend-process'; beforeEach(() => { - login(ROLE.endpoint_response_actions_access); + login(ROLE.soc_manager); loadRule().then((res) => { ruleId = res.id; ruleName = res.name; @@ -115,24 +153,30 @@ describe( visitRuleActions(ruleId); cy.getByTestSubj('edit-rule-actions-tab').click(); - cy.getByTestSubj(`response-actions-list-item-0`).within(() => { - cy.getByTestSubj('input').should('have.value', 'Isolate host'); - cy.getByTestSubj('input').should('have.value', 'Isolate host'); + cy.getByTestSubj(`response-actions-list-item-1`).within(() => { + cy.getByTestSubj('input').should('have.value', 'Suspend host'); cy.getByTestSubj('input').type(`{selectall}{backspace}${newDescription}`); - cy.getByTestSubj('commandTypeField').click(); + cy.getByTestSubj('config-overwrite-toggle').click(); + cy.getByTestSubj('config-custom-field-name').should('have.value', ''); + cy.getByTestSubj('config-overwrite-toggle').click(); + cy.getByTestSubj('config-custom-field-name').type('process.entity_id{downArrow}{enter}'); }); - validateAvailableCommands(); + cy.intercept('PUT', '/api/detection_engine/rules').as('updateResponseAction'); cy.getByTestSubj('ruleEditSubmitButton').click(); cy.wait('@updateResponseAction').should(({ request }) => { const query = { - action_type_id: ResponseActionTypesEnum['.endpoint'], params: { - command: testedCommand, + command: secondTestedCommand, comment: newDescription, + config: { + field: 'process.entity_id', + overwrite: false, + }, }, + action_type_id: ResponseActionTypesEnum['.endpoint'], }; - expect(request.body.response_actions[0]).to.deep.equal(query); + expect(request.body.response_actions[1]).to.deep.equal(query); }); cy.contains(`${ruleName} was saved`).should('exist'); }); @@ -147,7 +191,7 @@ describe( cy.intercept('PUT', '/api/detection_engine/rules').as('deleteResponseAction'); cy.getByTestSubj('ruleEditSubmitButton').click(); cy.wait('@deleteResponseAction').should(({ request }) => { - expect(request.body.response_actions).to.be.equal(undefined); + expect(request.body.response_actions.length).to.be.equal(2); }); cy.contains(`${ruleName} was saved`).should('exist'); }); @@ -157,9 +201,10 @@ describe( const [ruleName, ruleDescription] = generateRandomStringName(2); beforeEach(() => { - login(ROLE.endpoint_response_actions_no_access); + login(ROLE.rule_author); }); + // let currentUrl it('response actions are disabled', () => { fillUpNewRule(ruleName, ruleDescription); cy.getByTestSubj('response-actions-wrapper').within(() => { @@ -174,7 +219,7 @@ describe( let ruleId: string; beforeEach(() => { - login(ROLE.endpoint_response_actions_no_access); + login(ROLE.rule_author); loadRule().then((res) => { ruleId = res.id; }); @@ -200,8 +245,8 @@ describe( // Try removing action cy.getByTestSubj('remove-response-action').click({ force: true }); }); - cy.getByTestSubj(`response-actions-list-item-0`).should('exist'); - tryAddingDisabledResponseAction(1); + cy.getByTestSubj(`response-actions-list-item-2`).should('exist'); + tryAddingDisabledResponseAction(3); }); }); } diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/automated_response_actions/no_license.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/automated_response_actions/no_license.cy.ts index d1449672bed26..d120459b64ea3 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/automated_response_actions/no_license.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/automated_response_actions/no_license.cy.ts @@ -6,7 +6,6 @@ */ import { navigateToAlertsList } from '../../screens/alerts'; -import { disableExpandableFlyoutAdvancedSettings } from '../../tasks/common'; import { closeAllToasts } from '../../tasks/toasts'; import { fillUpNewRule } from '../../tasks/response_actions'; import { login, ROLE } from '../../tasks/login'; @@ -39,7 +38,6 @@ describe('No License', { tags: '@ess', env: { ftrConfig: { license: 'basic' } } const [endpointAgentId, endpointHostname] = generateRandomStringName(2); beforeEach(() => { login(); - disableExpandableFlyoutAdvancedSettings(); indexEndpointRuleAlerts({ endpointAgentId, endpointHostname, @@ -73,8 +71,8 @@ describe('No License', { tags: '@ess', env: { ftrConfig: { license: 'basic' } } navigateToAlertsList(`query=(language:kuery,query:'agent.id: "${endpointAgentId}" ')`); closeAllToasts(); cy.getByTestSubj('expand-event').first().click(); - cy.getByTestSubj('response-actions-notification').should('not.have.text', '0'); - cy.getByTestSubj('responseActionsViewTab').click(); + cy.getByTestSubj('securitySolutionFlyoutNavigationExpandDetailButton').click(); + cy.getByTestSubj('securitySolutionFlyoutResponseTab').click(); cy.contains('Permission denied'); cy.contains( 'To access these results, ask your administrator for Elastic Defend Kibana privileges.' diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/automated_response_actions/results.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/automated_response_actions/results.cy.ts index 10272e5600583..344c4b0b3bd21 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/automated_response_actions/results.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/automated_response_actions/results.cy.ts @@ -5,9 +5,8 @@ * 2.0. */ -import { disableExpandableFlyoutAdvancedSettings } from '../../tasks/common'; +import { navigateToAlertsList } from '../../screens/alerts'; import { generateRandomStringName } from '../../tasks/utils'; -import { APP_ALERTS_PATH } from '../../../../../common/constants'; import { closeAllToasts } from '../../tasks/toasts'; import { indexEndpointHosts } from '../../tasks/index_endpoint_hosts'; import type { ReturnTypeFromChainable } from '../../types'; @@ -50,57 +49,36 @@ describe('Results', { tags: ['@ess', '@serverless'] }, () => { } }); - describe( - 'see results when has RBAC', - { - // Not supported in serverless! - // The `disableExpandableFlyoutAdvancedSettings()` fails because the API - // `internal/kibana/settings` is not accessible in serverless - tags: ['@brokenInServerless'], - }, - () => { - before(() => { - login(ROLE.endpoint_response_actions_access); - disableExpandableFlyoutAdvancedSettings(); - }); + describe('see results when has RBAC', () => { + before(() => { + login(ROLE.soc_manager); + }); - it('see endpoint action', () => { - cy.visit(APP_ALERTS_PATH); - closeAllToasts(); - cy.getByTestSubj('expand-event').first().click(); - cy.getByTestSubj('response-actions-notification').should('not.have.text', '0'); - cy.getByTestSubj('responseActionsViewTab').click(); - cy.getByTestSubj('endpoint-results-comment'); - cy.contains(/isolate is pending|isolate completed successfully/g); - }); - } - ); - describe( - 'do not see results results when does not have RBAC', - { - // Not supported in serverless! - // The `disableExpandableFlyoutAdvancedSettings()` fails because the API - // `internal/kibana/settings` is not accessible in serverless - tags: ['@brokenInServerless'], - }, - () => { - before(() => { - login(ROLE.endpoint_response_actions_no_access); - disableExpandableFlyoutAdvancedSettings(); - }); + it('see endpoint action', () => { + navigateToAlertsList(`query=(language:kuery,query:'_id: ${alertData?.alerts[0]._id}')`); + closeAllToasts(); + cy.getByTestSubj('expand-event').first().click(); + cy.getByTestSubj('securitySolutionFlyoutNavigationExpandDetailButton').click(); + cy.getByTestSubj('securitySolutionFlyoutResponseTab').click(); + cy.contains(/isolate is pending|isolate completed successfully/g); + }); + }); + describe('do not see results results when does not have RBAC', () => { + before(() => { + login(ROLE.t1_analyst); + }); - it('show the permission denied callout', () => { - cy.visit(APP_ALERTS_PATH); - closeAllToasts(); + it('show the permission denied callout', () => { + navigateToAlertsList(`query=(language:kuery,query:'_id: ${alertData?.alerts[0]._id}')`); + closeAllToasts(); - cy.getByTestSubj('expand-event').first().click(); - cy.getByTestSubj('response-actions-notification').should('not.have.text', '0'); - cy.getByTestSubj('responseActionsViewTab').click(); - cy.contains('Permission denied'); - cy.contains( - 'To access these results, ask your administrator for Elastic Defend Kibana privileges.' - ); - }); - } - ); + cy.getByTestSubj('expand-event').first().click(); + cy.getByTestSubj('securitySolutionFlyoutNavigationExpandDetailButton').click(); + cy.getByTestSubj('securitySolutionFlyoutResponseTab').click(); + cy.contains('Permission denied'); + cy.contains( + 'To access these results, ask your administrator for Elastic Defend Kibana privileges.' + ); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/management/cypress/tasks/api_fixtures.ts b/x-pack/plugins/security_solution/public/management/cypress/tasks/api_fixtures.ts index c948062c994c0..1bd0e5652b442 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/tasks/api_fixtures.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/tasks/api_fixtures.ts @@ -69,6 +69,28 @@ export const loadRule = (body = {}, includeResponseActions = true) => params: { command: 'isolate', comment: 'Isolate host' }, action_type_id: '.endpoint', }, + { + params: { + command: 'suspend-process', + comment: 'Suspend host', + config: { + field: 'entity_id', + overwrite: false, + }, + }, + action_type_id: '.endpoint', + }, + { + params: { + command: 'kill-process', + comment: 'Kill host', + config: { + field: '', + overwrite: true, + }, + }, + action_type_id: '.endpoint', + }, ], } : {}), diff --git a/x-pack/plugins/security_solution/public/management/cypress/tasks/response_actions.ts b/x-pack/plugins/security_solution/public/management/cypress/tasks/response_actions.ts index 126d637f07edb..a03d5a2ed483d 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/tasks/response_actions.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/tasks/response_actions.ts @@ -25,11 +25,19 @@ import type { ResponseActionsApiCommandNames } from '../../../../common/endpoint import { ENABLED_AUTOMATED_RESPONSE_ACTION_COMMANDS } from '../../../../common/endpoint/service/response_actions/constants'; export const validateAvailableCommands = () => { - cy.get('[data-test-subj^="command-type"]').should( - 'have.length', - ENABLED_AUTOMATED_RESPONSE_ACTION_COMMANDS.length - ); - ENABLED_AUTOMATED_RESPONSE_ACTION_COMMANDS.forEach((command) => { + // TODO: TC- use ENABLED_AUTOMATED_RESPONSE_ACTION_COMMANDS when we go GA with automated process actions + const config = Cypress.config(); + const automatedActionsPAttern = /automatedProcessActionsEnabled/; + const automatedProcessActionsEnabled = + config.env.ftrConfig.kbnServerArgs[0].match(automatedActionsPAttern); + + const enabledActions = [ + ...ENABLED_AUTOMATED_RESPONSE_ACTION_COMMANDS, + ...(automatedProcessActionsEnabled ? ['kill-process', 'suspend-process'] : []), + ]; + + cy.get('[data-test-subj^="command-type"]').should('have.length', enabledActions.length); + enabledActions.forEach((command) => { cy.getByTestSubj(`command-type-${command}`); }); }; diff --git a/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_get_automated_action_list.ts b/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_get_automated_action_list.ts index 03828cba319c2..27b46eddeb042 100644 --- a/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_get_automated_action_list.ts +++ b/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_get_automated_action_list.ts @@ -151,7 +151,7 @@ const combineResponse = ( return { id: action.EndpointActions.action_id, - agents: action.agent.id as string[], + agents: Array.isArray(action.agent.id) ? action.agent.id : [action.agent.id], agentType: 'endpoint', parameters, ...(alertId?.length ? { alertIds: alertId } : {}), @@ -169,7 +169,7 @@ const combineResponse = ( completedAt: responseData?.completedAt, isCompleted: !!responseData?.isCompleted, isExpired: !!responseData?.isExpired, - wasSuccessful: !!responseData?.isCompleted, + wasSuccessful: responseData.status === 'successful', status: responseData.status, agentState: {}, errors: action.error ? [action.error.message as string] : undefined, diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/models/advanced_policy_schema.ts b/x-pack/plugins/security_solution/public/management/pages/policy/models/advanced_policy_schema.ts index 817eb2715b5b2..a9d97d96ee748 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/models/advanced_policy_schema.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/models/advanced_policy_schema.ts @@ -1523,6 +1523,39 @@ export const AdvancedPolicySchema: AdvancedPolicySchemaType[] = [ } ), }, + { + key: 'windows.advanced.alerts.sample_collection', + first_supported_version: '8.13', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.windows.advanced.alerts.sample_collection', + { + defaultMessage: + "A value of 'false' disables malicious sample collection for Windows alerts. Default: true.", + } + ), + }, + { + key: 'mac.advanced.alerts.sample_collection', + first_supported_version: '8.13', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.mac.advanced.alerts.sample_collection', + { + defaultMessage: + "A value of 'false' disables malicious sample collection for Mac alerts. Default: true.", + } + ), + }, + { + key: 'linux.advanced.alerts.sample_collection', + first_supported_version: '8.13', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.linux.advanced.alerts.sample_collection', + { + defaultMessage: + "A value of 'false' disables malicious sample collection for Linux alerts. Default: true.", + } + ), + }, { key: 'windows.advanced.events.disable_image_load_suppression_cache', first_supported_version: '8.12.1', diff --git a/x-pack/plugins/security_solution/public/management/pages/response_actions/view/response_actions_list_page.test.tsx b/x-pack/plugins/security_solution/public/management/pages/response_actions/view/response_actions_list_page.test.tsx index 19ce46111d1c7..65cd4ffc09908 100644 --- a/x-pack/plugins/security_solution/public/management/pages/response_actions/view/response_actions_list_page.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/response_actions/view/response_actions_list_page.test.tsx @@ -437,7 +437,7 @@ describe('Response actions history page', () => { }, []); expect(selectedFilterOptions.length).toEqual(1); - expect(selectedFilterOptions).toEqual(['Endpoint. Checked option.']); + expect(selectedFilterOptions).toEqual(['Elastic Defend. Checked option.']); expect(history.location.search).toEqual('?agentTypes=endpoint'); }); }); diff --git a/x-pack/plugins/security_solution/public/plugin.tsx b/x-pack/plugins/security_solution/public/plugin.tsx index b5e69d9eeada3..edda193f502fc 100644 --- a/x-pack/plugins/security_solution/public/plugin.tsx +++ b/x-pack/plugins/security_solution/public/plugin.tsx @@ -59,6 +59,7 @@ import type { SecurityAppStore } from './common/store/types'; import { PluginContract } from './plugin_contract'; import { TopValuesPopoverService } from './app/components/top_values_popover/top_values_popover_service'; import { parseConfigSettings, type ConfigSettings } from '../common/config_settings'; +import { getExternalReferenceAttachmentEndpointRegular } from './cases/attachments/external_reference'; export class Plugin implements IPlugin { /** @@ -108,6 +109,7 @@ export class Plugin implements IPlugin(); private storage = new Storage(localStorage); @@ -272,6 +274,10 @@ export class Plugin implements IPlugin = [ const DEFAULT_REGION = 'aws-eu-west-1'; const PROJECT_NAME_PREFIX = 'kibana-cypress-security-solution-ephemeral'; -const BASE_ENV_URL = 'https://console.qa.cld.elstc.co'; +const BASE_ENV_URL = `${process.env.QA_CONSOLE_URL}`; let log: ToolingLog; const API_HEADERS = Object.freeze({ 'kbn-xsrf': 'cypress-creds', @@ -96,6 +108,22 @@ async function createSecurityProject( product_types: productTypes, }; + log.info(`Kibana override flag equals to ${process.env.KIBANA_MKI_USE_LATEST_COMMIT}!`); + if ( + process.env.KIBANA_MKI_USE_LATEST_COMMIT && + process.env.KIBANA_MKI_USE_LATEST_COMMIT === '1' + ) { + const kibanaOverrideImage = `${process.env.BUILDKITE_COMMIT?.substring(0, 12)}`; + log.info( + `Overriding Kibana image in the MKI with docker.elastic.co/kibana-ci/kibana-serverless:sec-sol-qg-${kibanaOverrideImage}` + ); + body.overrides = { + kibana: { + docker_image: `docker.elastic.co/kibana-ci/kibana-serverless:sec-sol-qg-${kibanaOverrideImage}`, + }, + }; + } + try { const response = await axios.post(`${BASE_ENV_URL}/api/v1/serverless/projects/security`, body, { headers: { @@ -570,8 +598,11 @@ ${JSON.stringify(cypressConfigFile, null, 2)} KIBANA_USERNAME: credentials.username, KIBANA_PASSWORD: credentials.password, + // Both CLOUD_SERVERLESS and IS_SERVERLESS are used by the cypress tests. CLOUD_SERVERLESS: true, IS_SERVERLESS: true, + // TEST_CLOUD is used by SvlUserManagerProvider to define if testing against cloud. + TEST_CLOUD: 1, }; if (process.env.DEBUG && !process.env.CI) { diff --git a/x-pack/plugins/security_solution/server/config.mock.ts b/x-pack/plugins/security_solution/server/config.mock.ts index 855cec11ab16e..9f61523dbbe8b 100644 --- a/x-pack/plugins/security_solution/server/config.mock.ts +++ b/x-pack/plugins/security_solution/server/config.mock.ts @@ -22,6 +22,7 @@ export const createMockConfig = (): ConfigType => { maxTimelineImportPayloadBytes: 10485760, enableExperimental, packagerTaskInterval: '60s', + packagerTaskTimeout: '5m', packagerTaskPackagePolicyUpdateBatchSize: 10, prebuiltRulesPackageVersion: '', alertMergeStrategy: 'missingFields', diff --git a/x-pack/plugins/security_solution/server/config.ts b/x-pack/plugins/security_solution/server/config.ts index adc8fbfb1174c..4cb9ff479fff1 100644 --- a/x-pack/plugins/security_solution/server/config.ts +++ b/x-pack/plugins/security_solution/server/config.ts @@ -92,14 +92,20 @@ export const configSchema = schema.object({ }), /** - * Artifacts Configuration + * Endpoint Artifacts Configuration: the interval between runs of the task that builds the + * artifacts and associated manifest. */ packagerTaskInterval: schema.string({ defaultValue: '60s' }), + /** + * Endpoint Artifacts Configuration: timeout value for how long the task should run. + */ + packagerTaskTimeout: schema.string({ defaultValue: '20m' }), + /** * Artifacts Configuration for package policy update concurrency */ - packagerTaskPackagePolicyUpdateBatchSize: schema.number({ defaultValue: 10, max: 50, min: 1 }), + packagerTaskPackagePolicyUpdateBatchSize: schema.number({ defaultValue: 25, max: 50, min: 1 }), /** * For internal use. Specify which version of the Detection Rules fleet package to install diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/task.test.ts b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/task.test.ts index a0ad1f9712be1..a1988cb7a13ae 100644 --- a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/task.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/task.test.ts @@ -19,7 +19,7 @@ import { getMockArtifacts } from './mocks'; import { InvalidInternalManifestError } from '../../services/artifacts/errors'; import { loggingSystemMock } from '@kbn/core/server/mocks'; -describe('task', () => { +describe('Endpoint artifact packager task', () => { const MOCK_TASK_INSTANCE = { id: `${ManifestTaskConstants.TYPE}:1.0.0`, runAt: new Date(), @@ -170,7 +170,7 @@ describe('task', () => { await runTask(manifestManager); - expect(logger.info).toHaveBeenCalledWith('recovering from invalid internal manifest'); + expect(logger.warn).toHaveBeenCalledWith('recovering from invalid internal manifest'); expect(logger.error).toHaveBeenNthCalledWith(1, expect.any(InvalidInternalManifestError)); }); diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/task.ts b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/task.ts index dafa13141a0c6..8547eb6dca11c 100644 --- a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/task.ts +++ b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/task.ts @@ -22,6 +22,10 @@ import { wrapErrorIfNeeded } from '../../utils'; import { EndpointError } from '../../../../common/endpoint/errors'; export const ManifestTaskConstants = { + /** + * No longer used. Timeout value now comes from `xpack.securitySolution.packagerTaskTimeout` + * @deprecated + */ TIMEOUT: '1m', TYPE: 'endpoint:user-artifact-packager', VERSION: '1.0.0', @@ -44,22 +48,37 @@ export class ManifestTask { constructor(setupContract: ManifestTaskSetupContract) { this.endpointAppContext = setupContract.endpointAppContext; this.logger = this.endpointAppContext.logFactory.get(this.getTaskId()); + const { packagerTaskInterval, packagerTaskTimeout, packagerTaskPackagePolicyUpdateBatchSize } = + this.endpointAppContext.serverConfig; + + this.logger.info( + `Registering ${ManifestTaskConstants.TYPE} task with timeout of [${packagerTaskTimeout}], interval of [${packagerTaskInterval}] and policy update batch size of [${packagerTaskPackagePolicyUpdateBatchSize}]` + ); setupContract.taskManager.registerTaskDefinitions({ [ManifestTaskConstants.TYPE]: { title: 'Security Solution Endpoint Exceptions Handler', - timeout: ManifestTaskConstants.TIMEOUT, + timeout: packagerTaskTimeout, createTaskRunner: ({ taskInstance }: { taskInstance: ConcreteTaskInstance }) => { return { run: async () => { - const taskInterval = (await this.endpointAppContext.config()).packagerTaskInterval; - const startTime = new Date().getTime(); + const taskInterval = packagerTaskInterval; + const startTime = new Date(); + + this.logger.info(`Started. Checking for changes to endpoint artifacts`); + await this.runTask(taskInstance.id); + const endTime = new Date().getTime(); - this.logger.debug( - `${ManifestTaskConstants.TYPE} task run took ${endTime - startTime}ms` + + this.logger.info( + `Complete. Task run took ${ + endTime - startTime.getTime() + }ms [ stated: ${startTime.toISOString()} ]` ); + const nextRun = new Date(); + if (taskInterval.endsWith('s')) { const seconds = parseInt(taskInterval.slice(0, -1), 10); nextRun.setSeconds(nextRun.getSeconds() + seconds); @@ -70,12 +89,20 @@ export class ManifestTask { this.logger.error(`Invalid task interval: ${taskInterval}`); return; } + return { state: {}, runAt: nextRun, }; }, - cancel: async () => {}, + cancel: async () => { + // TODO:PT add support for AbortController to Task manager + this.logger.warn( + 'Task run was canceled. Packaging of endpoint artifacts may be taking longer due to the ' + + 'amount of policies/artifacts. Consider increasing the `xpack.securitySolution.packagerTaskTimeout` ' + + 'server configuration setting if this continues' + ); + }, }; }, }, @@ -91,7 +118,7 @@ export class ManifestTask { taskType: ManifestTaskConstants.TYPE, scope: ['securitySolution'], schedule: { - interval: (await this.endpointAppContext.config()).packagerTaskInterval, + interval: this.endpointAppContext.serverConfig.packagerTaskInterval, }, state: {}, params: { version: ManifestTaskConstants.VERSION }, @@ -127,23 +154,29 @@ export class ManifestTask { } try { - let oldManifest: Manifest | null; + let oldManifest: Manifest | null = null; try { // Last manifest we computed, which was saved to ES oldManifest = await manifestManager.getLastComputedManifest(); } catch (e) { + this.logger.error(e); + // Lets recover from a failure in getting the internal manifest map by creating an empty default manifest if (e instanceof InvalidInternalManifestError) { - this.logger.error(e); - this.logger.info('recovering from invalid internal manifest'); + this.logger.warn('recovering from invalid internal manifest'); oldManifest = ManifestManager.createDefaultManifest(); + } else { + this.logger.error( + `unable to recover from error while attempting to retrieve last computed manifest` + ); + + return; } } - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - if (oldManifest! == null) { - this.logger.debug('Last computed manifest not available yet'); + if (!oldManifest) { + this.logger.info('Last computed manifest not available yet'); return; } @@ -152,10 +185,17 @@ export class ManifestTask { const diff = newManifest.diff(oldManifest); + this.logger.debug( + `New -vs- old manifest diff counts: ${Object.entries(diff).map( + ([diffType, diffItems]) => `${diffType}: ${diffItems.length}` + )}` + ); + const persistErrors = await manifestManager.pushArtifacts( diff.additions as InternalArtifactCompleteSchema[], newManifest ); + if (persistErrors.length) { reportErrors(this.logger, persistErrors); throw new Error('Unable to persist new artifacts.'); @@ -167,8 +207,9 @@ export class ManifestTask { await manifestManager.commit(newManifest); } - // Try dispatching to ingest-manager package policies + // Dispatch updates to Fleet integration policies with new manifest info const dispatchErrors = await manifestManager.tryDispatch(newManifest); + if (dispatchErrors.length) { reportErrors(this.logger, dispatchErrors); throw new Error('Error dispatching manifest.'); @@ -178,9 +219,11 @@ export class ManifestTask { const deleteErrors = await manifestManager.deleteArtifacts( diff.removals.map((artifact) => getArtifactId(artifact)) ); + if (deleteErrors.length) { reportErrors(this.logger, deleteErrors); } + await manifestManager.cleanup(newManifest); } catch (err) { this.logger.error(wrapErrorIfNeeded(err)); 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 74f222b64d2b9..4789ff8046e6d 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 @@ -1092,6 +1092,12 @@ describe('Response actions', () => { id: '123-456', }, }, + agent: { + id: '123-456', + }, + host: { + hostname: 'test-host', + }, }, ]), }); @@ -1215,6 +1221,12 @@ describe('Response actions', () => { id: '123-456', }, }, + agent: { + id: '123-456', + }, + host: { + hostname: 'test-host', + }, }, ]), }); diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/endpoint_actions_client.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/endpoint_actions_client.ts index 2acc6dec8f2a2..9e3c158033835 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/endpoint_actions_client.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/endpoint_actions_client.ts @@ -5,13 +5,11 @@ * 2.0. */ -import { stringify } from '../../../utils/stringify'; import type { HapiReadableStream } from '../../../../types'; import type { ResponseActionsApiCommandNames, ResponseActionAgentType, } from '../../../../../common/endpoint/service/response_actions/constants'; -import { updateCases } from '../create/update_cases'; import type { CreateActionPayload } from '../create/types'; import type { ExecuteActionRequestBody, @@ -36,6 +34,8 @@ import type { ResponseActionUploadOutputContent, ResponseActionUploadParameters, SuspendProcessActionOutputContent, + ImmutableObject, + HostMetadataInterface, } from '../../../../../common/endpoint/types'; export class EndpointActionsClient extends ResponseActionsClientImpl { @@ -80,16 +80,19 @@ export class EndpointActionsClient extends ResponseActionsClientImpl { .getActionCreateService() .createAction(createPayload, agentIds.valid); - try { - await updateCases({ - casesClient: this.options.casesClient, - endpointData: agentIds.hosts, - createActionPayload: createPayload, - }); - } catch (err) { - // failures during update of cases should not cause the response action to fail. Just log error - this.log.warn(`failed to update cases: ${err.message}\n${stringify(err)}`); - } + await this.updateCases({ + command, + comment: options.comment, + caseIds: options.case_ids, + alertIds: options.alert_ids, + actionId: response.id, + hosts: agentIds.hosts.map((endpoint: ImmutableObject) => { + return { + hostId: endpoint.agent.id, + hostname: endpoint.host.hostname, + }; + }), + }); return response as TResponse; } diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/lib/base_response_actions_client.test.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/lib/base_response_actions_client.test.ts index aaceb2d6aef75..f23bafe0f22a0 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/lib/base_response_actions_client.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/lib/base_response_actions_client.test.ts @@ -120,6 +120,7 @@ describe('ResponseActionsClientImpl base class', () => { caseIds: ['case-999'], alertIds: [KNOWN_ALERT_ID_1, KNOWN_ALERT_ID_2, KNOWN_ALERT_ID_3], comment: 'this is a case comment', + actionId: 'action-123', hosts: [ { hostId: '1-2-3', @@ -201,76 +202,29 @@ describe('ResponseActionsClientImpl base class', () => { expect(casesClient.attachments.bulkCreate).toHaveBeenLastCalledWith({ attachments: [ { - actions: { - targets: [ - { - endpointId: '1-2-3', - hostname: 'foo-one', - }, - { - endpointId: '4-5-6', - hostname: 'foo-two', - }, - ], - type: 'isolate', - }, - comment: 'this is a case comment', + externalReferenceAttachmentTypeId: 'endpoint', + externalReferenceId: 'action-123', owner: 'securitySolution', - type: 'actions', - }, - { - actions: { - targets: [ - { - endpointId: '1-2-3', - hostname: 'foo-one', - }, - { - endpointId: '4-5-6', - hostname: 'foo-two', - }, - ], - type: 'isolate', - }, - comment: 'this is a case comment', - owner: 'securitySolution', - type: 'actions', - }, - { - actions: { - targets: [ - { - endpointId: '1-2-3', - hostname: 'foo-one', - }, - { - endpointId: '4-5-6', - hostname: 'foo-two', - }, - ], - type: 'isolate', + externalReferenceStorage: { + type: 'elasticSearchDoc', }, - comment: 'this is a case comment', - owner: 'securitySolution', - type: 'actions', - }, - { - actions: { + type: 'externalReference', + externalReferenceMetadata: { + command: 'isolate', + comment: 'this is a case comment', targets: [ { endpointId: '1-2-3', hostname: 'foo-one', + agentType: 'endpoint', }, { endpointId: '4-5-6', hostname: 'foo-two', + agentType: 'endpoint', }, ], - type: 'isolate', }, - comment: 'this is a case comment', - owner: 'securitySolution', - type: 'actions', }, ], caseId: 'case-3', @@ -280,7 +234,7 @@ describe('ResponseActionsClientImpl base class', () => { it('should not error if update to a case fails', async () => { (casesClient.attachments.bulkCreate as jest.Mock).mockImplementation(async (options) => { if (options.caseId === 'case-2') { - throw new Error('update filed to case-2'); + throw new Error('update failed to case-2'); } }); await baseClassMock.updateCases(updateCasesOptions); 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 68e8db5fa1e69..356b9f129f79e 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 @@ -9,8 +9,9 @@ import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import type { CasesClient } from '@kbn/cases-plugin/server'; import type { Logger } from '@kbn/logging'; import { v4 as uuidv4 } from 'uuid'; -import { AttachmentType } from '@kbn/cases-plugin/common'; -import type { BulkCreateArgs } from '@kbn/cases-plugin/server/client/attachments/types'; +import { AttachmentType, ExternalReferenceStorageType } from '@kbn/cases-plugin/common'; +import type { CaseAttachments } from '@kbn/cases-plugin/public/types'; + import type { EndpointAppContextService } from '../../../../endpoint_app_context_services'; import { APP_ID } from '../../../../../../common'; import type { @@ -57,6 +58,8 @@ import type { } from '../../../../../../common/api/endpoint'; import type { CreateActionPayload } from '../../create/types'; import { stringify } from '../../../../utils/stringify'; +import { CASE_ATTACHMENT_ENDPOINT_TYPE_ID } from '../../../../../../common/constants'; +import { EMPTY_COMMENT } from '../../../../utils/translations'; export interface ResponseActionsClientOptions { endpointService: EndpointAppContextService; @@ -79,6 +82,8 @@ export interface ResponseActionsClientUpdateCasesOptions { alertIds?: string[]; /** Comment to include in the Case attachment */ comment?: string; + /** The id of the action that was taken */ + actionId: string; } export type ResponseActionsClientWriteActionRequestToEndpointIndexOptions = @@ -119,6 +124,7 @@ export abstract class ResponseActionsClientImpl implements ResponseActionsClient caseIds = [], alertIds = [], comment = '', + actionId, }: ResponseActionsClientUpdateCasesOptions): Promise { if (caseIds.length === 0 && alertIds.length === 0) { this.log.debug(`Nothing to do. 'caseIds' and 'alertIds' are empty`); @@ -166,32 +172,43 @@ export abstract class ResponseActionsClientImpl implements ResponseActionsClient this.log.debug(`Updating cases:\n${stringify(allCases)}`); - // Create an attachment for each case that includes info. about the response actions taken against the hosts - const attachments = allCases.map(() => ({ - type: AttachmentType.actions, - comment, - actions: { - targets: hosts.map(({ hostId: endpointId, hostname }) => ({ endpointId, hostname })), - type: command, + const attachments: CaseAttachments = [ + { + type: AttachmentType.externalReference, + externalReferenceId: actionId, + externalReferenceStorage: { + type: ExternalReferenceStorageType.elasticSearchDoc, + }, + externalReferenceAttachmentTypeId: CASE_ATTACHMENT_ENDPOINT_TYPE_ID, + externalReferenceMetadata: { + targets: hosts.map(({ hostId: endpointId, hostname }) => { + return { + endpointId, + hostname, + agentType: this.agentType, + }; + }), + command, + comment: comment || EMPTY_COMMENT, + }, + owner: APP_ID, }, - owner: APP_ID, - })) as BulkCreateArgs['attachments']; + ]; const casesUpdateResponse = await Promise.all( - allCases.map((caseId) => - casesClient.attachments - .bulkCreate({ + allCases.map(async (caseId) => { + try { + return await casesClient.attachments.bulkCreate({ caseId, attachments, - }) - .catch((err) => { - // Log any error, BUT: do not fail execution - this.log.warn( - `Attempt to update case ID [${caseId}] failed: ${err.message}\n${stringify(err)}` - ); - return null; - }) - ) + }); + } catch (err) { + this.log.warn( + `Attempt to update case ID [${caseId}] failed: ${err.message}\n${stringify(err)}` + ); + return null; + } + }) ); this.log.debug(`Update to cases done:\n${stringify(casesUpdateResponse)}`); 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 d196232e83916..dfac74fc8a407 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 @@ -182,11 +182,10 @@ export class SentinelOneActionsClient extends ResponseActionsClientImpl { await this.validateRequest(options); await this.sendAction(SUB_ACTION.ISOLATE_HOST, { uuid: options.endpoint_ids[0] }); - const reqIndexOptions: ResponseActionsClientWriteActionRequestToEndpointIndexOptions = { + const actionRequestDoc = await this.writeActionRequestToEndpointIndex({ ...options, command: 'isolate', - }; - const actionRequestDoc = await this.writeActionRequestToEndpointIndex(reqIndexOptions); + }); await this.writeActionResponseToEndpointIndex({ actionId: actionRequestDoc.EndpointActions.action_id, agentId: actionRequestDoc.agent.id, @@ -194,6 +193,19 @@ export class SentinelOneActionsClient extends ResponseActionsClientImpl { command: actionRequestDoc.EndpointActions.data.command, }, }); + await this.updateCases({ + command: 'isolate', + caseIds: options.case_ids, + alertIds: options.alert_ids, + hosts: options.endpoint_ids.map((agentId) => { + return { + hostId: agentId, + hostname: actionRequestDoc.EndpointActions.data.hosts?.[agentId].name ?? '', + }; + }), + comment: options.comment, + actionId: actionRequestDoc.EndpointActions.action_id, + }); return this.fetchActionDetails(actionRequestDoc.EndpointActions.action_id); } @@ -216,6 +228,19 @@ export class SentinelOneActionsClient extends ResponseActionsClientImpl { command: actionRequestDoc.EndpointActions.data.command, }, }); + await this.updateCases({ + command: 'unisolate', + caseIds: options.case_ids, + alertIds: options.alert_ids, + hosts: options.endpoint_ids.map((agentId) => { + return { + hostId: agentId, + hostname: actionRequestDoc.EndpointActions.data.hosts?.[agentId].name ?? '', + }; + }), + comment: options.comment, + actionId: actionRequestDoc.EndpointActions.action_id, + }); return this.fetchActionDetails(actionRequestDoc.EndpointActions.action_id); } diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/create/action_errors.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/create/action_errors.ts index bb17cb2c5ac8a..8f68072e95707 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/create/action_errors.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/create/action_errors.ts @@ -7,17 +7,19 @@ import type { LicenseType } from '@kbn/licensing-plugin/common/types'; import type { EcsError } from '@kbn/ecs'; -import { validateAgents, validateEndpointLicense } from './validate'; +import { validateAgents, validateAlertError, validateEndpointLicense } from './validate'; import type { LicenseService } from '../../../../../common/license/license'; export const addErrorsToActionIfAny = ({ agents, licenseService, minimumLicenseRequired = 'basic', + error, }: { agents: string[]; licenseService: LicenseService; minimumLicenseRequired: LicenseType; + error?: string; }): | { error: { @@ -28,7 +30,8 @@ export const addErrorsToActionIfAny = ({ | undefined => { const licenseError = validateEndpointLicense(licenseService, minimumLicenseRequired); const agentsError = validateAgents(agents); - const alertActionError = licenseError || agentsError; + const actionError = validateAlertError(error); + const alertActionError = licenseError || agentsError || actionError; if (alertActionError) { return { diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/create/update_cases.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/create/update_cases.ts deleted file mode 100644 index 7032a0d7b4725..0000000000000 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/create/update_cases.ts +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { GetRelatedCasesByAlertResponse } from '@kbn/cases-plugin/common'; -import { AttachmentType } from '@kbn/cases-plugin/common'; -import type { CasesClient } from '@kbn/cases-plugin/server'; -import type { BulkCreateArgs } from '@kbn/cases-plugin/server/client/attachments/types'; -import { i18n } from '@kbn/i18n'; -import { APP_ID } from '../../../../../common'; -import type { - ImmutableObject, - HostMetadataInterface, - HostMetadata, -} from '../../../../../common/endpoint/types'; -import type { CreateActionPayload } from './types'; - -export const updateCases = async ({ - casesClient, - createActionPayload, - endpointData, -}: { - casesClient?: CasesClient; - createActionPayload: CreateActionPayload; - endpointData: Array>; -}): Promise => { - if (!casesClient) { - return; - } - // convert any alert IDs into cases - let caseIDs: string[] = createActionPayload.case_ids?.slice() || []; - if (createActionPayload.alert_ids && createActionPayload.alert_ids.length > 0) { - const newIDs: string[][] = await Promise.all( - createActionPayload.alert_ids.map(async (alertID: string) => { - const cases: GetRelatedCasesByAlertResponse = await casesClient.cases.getCasesByAlertID({ - alertID, - options: { owner: APP_ID }, - }); - return cases.map((caseInfo): string => { - return caseInfo.id; - }); - }) - ); - caseIDs = caseIDs.concat(...newIDs); - } - caseIDs = [...new Set(caseIDs)]; - - // Update all cases with a comment - if (caseIDs.length > 0) { - const targets = endpointData.map((endpoint: HostMetadata) => ({ - hostname: endpoint.host.hostname, - endpointId: endpoint.agent.id, - })); - - const attachments = caseIDs.map(() => ({ - type: AttachmentType.actions, - comment: createActionPayload.comment || EMPTY_COMMENT, - actions: { - targets, - type: createActionPayload.command, - }, - owner: APP_ID, - })) as BulkCreateArgs['attachments']; - - await Promise.all( - caseIDs.map((caseId) => - casesClient.attachments.bulkCreate({ - caseId, - attachments, - }) - ) - ); - } -}; - -export const EMPTY_COMMENT = i18n.translate( - 'xpack.securitySolution.endpoint.updateCases.emptyComment', - { - defaultMessage: 'No comment provided', - } -); diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/create/validate.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/create/validate.ts index 5a6fd84989109..cb831021501c3 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/create/validate.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/create/validate.ts @@ -9,7 +9,10 @@ import { i18n } from '@kbn/i18n'; import type { LicenseType } from '@kbn/licensing-plugin/server'; import type { LicenseService } from '../../../../../common/license'; -export const validateEndpointLicense = (license: LicenseService, licenseType: LicenseType) => { +export const validateEndpointLicense = ( + license: LicenseService, + licenseType: LicenseType +): string | undefined => { const hasEnterpriseLicense = license.isAtLeast(licenseType); if (!hasEnterpriseLicense) { @@ -17,12 +20,18 @@ export const validateEndpointLicense = (license: LicenseService, licenseType: Li } }; -export const validateAgents = (agents: string[]) => { +export const validateAgents = (agents: string[]): string | undefined => { if (!agents.length) { return HOST_NOT_ENROLLED; } }; +export const validateAlertError = (field?: string): string | undefined => { + if (field) { + return FIELD_NOT_EXIST(field); + } +}; + export const LICENSE_TOO_LOW = i18n.translate( 'xpack.securitySolution.responseActionsList.error.licenseTooLow', { @@ -36,3 +45,9 @@ export const HOST_NOT_ENROLLED = i18n.translate( defaultMessage: 'The host does not have Elastic Defend integration installed', } ); + +export const FIELD_NOT_EXIST = (field: string): string => + i18n.translate('xpack.securitySolution.responseActionsList.error.nonExistingFieldName', { + defaultMessage: 'The action was called with a non-existing event field name: {field}', + values: { field }, + }); diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/create/write_action_to_indices.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/create/write_action_to_indices.ts index a0626b2e4d883..3e86e8fa9625c 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/create/write_action_to_indices.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/create/write_action_to_indices.ts @@ -72,6 +72,7 @@ export const writeActionToIndices = async ({ agents, licenseService, minimumLicenseRequired, + error: payload.error, }), ...addRuleInfoToAction(payload), }; diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/artifact_client.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/artifact_client.ts index 3e00310a5bb64..24c04ee881b8e 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/artifact_client.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/artifact_client.ts @@ -11,9 +11,11 @@ import type { ListArtifactsProps, } from '@kbn/fleet-plugin/server'; import type { ListResult } from '@kbn/fleet-plugin/common'; +import type { FetchAllArtifactsOptions } from '@kbn/fleet-plugin/server/services'; import type { InternalArtifactCompleteSchema } from '../../schemas/artifacts'; -export interface EndpointArtifactClientInterface { +export interface EndpointArtifactClientInterface + extends Pick { getArtifact(id: string): Promise; createArtifact(artifact: InternalArtifactCompleteSchema): Promise; @@ -67,6 +69,15 @@ export class EndpointArtifactClient implements EndpointArtifactClientInterface { return this.fleetArtifacts.listArtifacts(options); } + fetchAll({ + // Our default, unlike the Fleet service, is to NOT include the body of + // the artifact, since we really don't need it when processing all artifacts + includeArtifactBody = false, + ...options + }: FetchAllArtifactsOptions = {}): AsyncIterable { + return this.fleetArtifacts.fetchAll({ ...options, includeArtifactBody }); + } + async createArtifact( artifact: InternalArtifactCompleteSchema ): Promise { diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts index 9ff37c67e613d..1d935ccb905d0 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts @@ -39,6 +39,10 @@ import { EndpointError } from '../../../../../common/endpoint/errors'; import type { Artifact } from '@kbn/fleet-plugin/server'; import { AppFeatureSecurityKey } from '@kbn/security-solution-features/keys'; import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types/src/response/exception_list_item_schema'; +import { + createFetchAllArtifactsIterableMock, + generateArtifactMock, +} from '@kbn/fleet-plugin/server/services/artifacts/mocks'; const getArtifactObject = (artifact: InternalArtifactSchema) => JSON.parse(Buffer.from(artifact.body!, 'base64').toString()); @@ -76,12 +80,9 @@ describe('ManifestManager', () => { const ARTIFACT_NAME_BLOCKLISTS_WINDOWS = 'endpoint-blocklist-windows-v1'; const ARTIFACT_NAME_BLOCKLISTS_LINUX = 'endpoint-blocklist-linux-v1'; - const mockPolicyListIdsResponse = (items: string[]) => - jest.fn().mockResolvedValue({ - items, - page: 1, - per_page: 100, - total: items.length, + const getMockPolicyFetchAllItemIds = (items: string[]) => + jest.fn(async function* () { + yield items; }); let ARTIFACTS: InternalArtifactCompleteSchema[] = []; @@ -200,9 +201,7 @@ describe('ManifestManager', () => { ( manifestManagerContext.artifactClient as jest.Mocked - ).listArtifacts.mockImplementation(async () => { - return { items: ARTIFACTS as Artifact[], total: 100, page: 1, perPage: 100 }; - }); + ).fetchAll.mockReturnValue(createFetchAllArtifactsIterableMock([ARTIFACTS as Artifact[]])); const manifest = await manifestManager.getLastComputedManifest(); @@ -259,33 +258,26 @@ describe('ManifestManager', () => { ( manifestManagerContext.artifactClient as jest.Mocked - ).listArtifacts.mockImplementation(async () => { - // report the MACOS Exceptions artifact as not found - return { - items: [ + ).fetchAll.mockReturnValue( + createFetchAllArtifactsIterableMock([ + // report the MACOS Exceptions artifact as not found + [ ARTIFACT_TRUSTED_APPS_MACOS, ARTIFACT_EXCEPTIONS_WINDOWS, ARTIFACT_TRUSTED_APPS_WINDOWS, ARTIFACTS_BY_ID[ARTIFACT_ID_EXCEPTIONS_LINUX], ] as Artifact[], - total: 100, - page: 1, - perPage: 100, - }; - }); + ]) + ); const manifest = await manifestManager.getLastComputedManifest(); expect(manifest?.getAllArtifacts()).toStrictEqual(ARTIFACTS.slice(1, 5)); - expect(manifestManagerContext.logger.error).toHaveBeenCalledWith( - new InvalidInternalManifestError( - `artifact id [${ARTIFACT_ID_EXCEPTIONS_MACOS}] not found!`, - { - entry: ARTIFACTS_BY_ID[ARTIFACT_ID_EXCEPTIONS_MACOS], - action: 'removed from internal ManifestManger tracking map', - } - ) + expect(manifestManagerContext.logger.warn).toHaveBeenCalledWith( + "Missing artifacts detected! Internal artifact manifest (SavedObject version [2.0.0]) references [1] artifact IDs that don't exist.\n" + + "First 10 below (run with logging set to 'debug' to see all):\n" + + 'endpoint-exceptionlist-macos-v1-96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3' ); }); }); @@ -327,7 +319,9 @@ describe('ManifestManager', () => { const manifestManager = new ManifestManager(context); context.exceptionListClient.findExceptionListItem = mockFindExceptionListItemResponses({}); - context.packagePolicyService.listIds = mockPolicyListIdsResponse([TEST_POLICY_ID_1]); + context.packagePolicyService.fetchAllItemIds = getMockPolicyFetchAllItemIds([ + TEST_POLICY_ID_1, + ]); context.savedObjectsClient.create = jest .fn() @@ -389,7 +383,9 @@ describe('ManifestManager', () => { .mockImplementation((_type: string, object: InternalManifestSchema) => ({ attributes: object, })); - context.packagePolicyService.listIds = mockPolicyListIdsResponse([TEST_POLICY_ID_1]); + context.packagePolicyService.fetchAllItemIds = getMockPolicyFetchAllItemIds([ + TEST_POLICY_ID_1, + ]); const manifest = await manifestManager.buildNewManifest(); @@ -460,7 +456,9 @@ describe('ManifestManager', () => { context.exceptionListClient.findExceptionListItem = mockFindExceptionListItemResponses({ [ENDPOINT_LIST_ID]: { macos: [exceptionListItem] }, }); - context.packagePolicyService.listIds = mockPolicyListIdsResponse([TEST_POLICY_ID_1]); + context.packagePolicyService.fetchAllItemIds = getMockPolicyFetchAllItemIds([ + TEST_POLICY_ID_1, + ]); context.savedObjectsClient.create = jest .fn() .mockImplementation((_type: string, object: InternalManifestSchema) => ({ @@ -576,7 +574,7 @@ describe('ManifestManager', () => { .mockImplementation((_type: string, object: InternalManifestSchema) => ({ attributes: object, })); - context.packagePolicyService.listIds = mockPolicyListIdsResponse([ + context.packagePolicyService.fetchAllItemIds = getMockPolicyFetchAllItemIds([ TEST_POLICY_ID_1, TEST_POLICY_ID_2, ]); @@ -679,7 +677,7 @@ describe('ManifestManager', () => { linux: [trustedAppListItem, trustedAppListItemPolicy2], }, }); - context.packagePolicyService.listIds = mockPolicyListIdsResponse([ + context.packagePolicyService.fetchAllItemIds = getMockPolicyFetchAllItemIds([ TEST_POLICY_ID_1, TEST_POLICY_ID_2, ]); @@ -795,7 +793,9 @@ describe('ManifestManager', () => { .mockImplementation((_type: string, object: InternalManifestSchema) => ({ attributes: object, })); - context.packagePolicyService.listIds = mockPolicyListIdsResponse([TEST_POLICY_ID_1]); + context.packagePolicyService.fetchAllItemIds = getMockPolicyFetchAllItemIds([ + TEST_POLICY_ID_1, + ]); const manifest = await manifestManager.buildNewManifest(); @@ -878,7 +878,9 @@ describe('ManifestManager', () => { .mockImplementation((_type: string, object: InternalManifestSchema) => ({ attributes: object, })); - context.packagePolicyService.listIds = mockPolicyListIdsResponse([TEST_POLICY_ID_1]); + context.packagePolicyService.fetchAllItemIds = getMockPolicyFetchAllItemIds([ + TEST_POLICY_ID_1, + ]); const manifest = await manifestManager.buildNewManifest(); @@ -960,7 +962,9 @@ describe('ManifestManager', () => { .mockImplementation((_type: string, object: InternalManifestSchema) => ({ attributes: object, })); - context.packagePolicyService.listIds = mockPolicyListIdsResponse([TEST_POLICY_ID_1]); + context.packagePolicyService.fetchAllItemIds = getMockPolicyFetchAllItemIds([ + TEST_POLICY_ID_1, + ]); const manifest = await manifestManager.buildNewManifest(); @@ -1026,7 +1030,9 @@ describe('ManifestManager', () => { .mockImplementation((_type: string, object: InternalManifestSchema) => ({ attributes: object, })); - context.packagePolicyService.listIds = mockPolicyListIdsResponse([TEST_POLICY_ID_1]); + context.packagePolicyService.fetchAllItemIds = getMockPolicyFetchAllItemIds([ + TEST_POLICY_ID_1, + ]); const manifest = await manifestManager.buildNewManifest(); @@ -1068,7 +1074,9 @@ describe('ManifestManager', () => { .mockImplementation((_type: string, object: InternalManifestSchema) => ({ attributes: object, })); - context.packagePolicyService.listIds = mockPolicyListIdsResponse([TEST_POLICY_ID_1]); + context.packagePolicyService.fetchAllItemIds = getMockPolicyFetchAllItemIds([ + TEST_POLICY_ID_1, + ]); const manifest = await manifestManager.buildNewManifest(); @@ -1299,12 +1307,9 @@ describe('ManifestManager', () => { }); describe('tryDispatch', () => { - const mockPolicyListResponse = (items: PackagePolicy[]) => - jest.fn().mockResolvedValue({ - items, - page: 1, - per_page: 100, - total: items.length, + const getMockPolicyFetchAllItems = (items: PackagePolicy[]) => + jest.fn(async function* () { + yield items; }); test('Should not dispatch if no policies', async () => { @@ -1313,8 +1318,7 @@ describe('ManifestManager', () => { const manifest = new Manifest({ soVersion: '1.0.0' }); manifest.addEntry(ARTIFACT_EXCEPTIONS_MACOS); - - context.packagePolicyService.list = mockPolicyListResponse([]); + context.packagePolicyService.fetchAllItems = getMockPolicyFetchAllItems([]); await expect(manifestManager.tryDispatch(manifest)).resolves.toStrictEqual([]); @@ -1328,7 +1332,7 @@ describe('ManifestManager', () => { const manifest = new Manifest({ soVersion: '1.0.0' }); manifest.addEntry(ARTIFACT_EXCEPTIONS_MACOS); - context.packagePolicyService.list = mockPolicyListResponse([ + context.packagePolicyService.fetchAllItems = getMockPolicyFetchAllItems([ createPackagePolicyWithConfigMock({ id: TEST_POLICY_ID_1 }), ]); @@ -1346,7 +1350,7 @@ describe('ManifestManager', () => { const manifest = new Manifest({ soVersion: '1.0.0' }); manifest.addEntry(ARTIFACT_EXCEPTIONS_MACOS); - context.packagePolicyService.list = mockPolicyListResponse([ + context.packagePolicyService.fetchAllItems = getMockPolicyFetchAllItems([ createPackagePolicyWithConfigMock({ id: TEST_POLICY_ID_1, config: { @@ -1378,7 +1382,7 @@ describe('ManifestManager', () => { manifest.addEntry(ARTIFACT_EXCEPTIONS_WINDOWS, TEST_POLICY_ID_2); manifest.addEntry(ARTIFACT_TRUSTED_APPS_MACOS, TEST_POLICY_ID_2); - context.packagePolicyService.list = mockPolicyListResponse([ + context.packagePolicyService.fetchAllItems = getMockPolicyFetchAllItems([ createPackagePolicyWithConfigMock({ id: TEST_POLICY_ID_1, config: { @@ -1446,7 +1450,7 @@ describe('ManifestManager', () => { manifest.addEntry(ARTIFACT_EXCEPTIONS_WINDOWS, TEST_POLICY_ID_2); manifest.addEntry(ARTIFACT_TRUSTED_APPS_MACOS, TEST_POLICY_ID_2); - context.packagePolicyService.list = mockPolicyListResponse([ + context.packagePolicyService.fetchAllItems = getMockPolicyFetchAllItems([ createPackagePolicyWithConfigMock({ id: TEST_POLICY_ID_1, config: { @@ -1516,7 +1520,7 @@ describe('ManifestManager', () => { const manifest = new Manifest({ soVersion: '1.0.0', semanticVersion: '1.0.1' }); manifest.addEntry(ARTIFACT_EXCEPTIONS_MACOS); - context.packagePolicyService.list = mockPolicyListResponse([ + context.packagePolicyService.fetchAllItems = getMockPolicyFetchAllItems([ createPackagePolicyWithConfigMock({ id: TEST_POLICY_ID_1, config: { @@ -1557,8 +1561,14 @@ describe('ManifestManager', () => { const context = buildManifestManagerContextMock({}); const manifestManager = new ManifestManager(context); + (context.artifactClient.fetchAll as jest.Mock).mockReturnValue( + createFetchAllArtifactsIterableMock([[generateArtifactMock()]]) + ); + context.exceptionListClient.findExceptionListItem = mockFindExceptionListItemResponses({}); - context.packagePolicyService.listIds = mockPolicyListIdsResponse([TEST_POLICY_ID_1]); + context.packagePolicyService.fetchAllItemIds = getMockPolicyFetchAllItemIds([ + TEST_POLICY_ID_1, + ]); context.savedObjectsClient.create = jest .fn() @@ -1581,7 +1591,9 @@ describe('ManifestManager', () => { const manifestManager = new ManifestManager(context); context.exceptionListClient.findExceptionListItem = mockFindExceptionListItemResponses({}); - context.packagePolicyService.listIds = mockPolicyListIdsResponse([TEST_POLICY_ID_1]); + context.packagePolicyService.fetchAllItemIds = getMockPolicyFetchAllItemIds([ + TEST_POLICY_ID_1, + ]); context.savedObjectsClient.create = jest .fn() diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts index 27f528aba2716..a1ec74bc57b09 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts @@ -6,15 +6,17 @@ */ import semver from 'semver'; -import { chunk, isEmpty, isEqual, keyBy } from 'lodash'; +import { isEmpty, isEqual, keyBy } from 'lodash'; import type { ElasticsearchClient } from '@kbn/core/server'; import { type Logger, type SavedObjectsClientContract } from '@kbn/core/server'; import { ENDPOINT_LIST_ID, ENDPOINT_ARTIFACT_LISTS } from '@kbn/securitysolution-list-constants'; -import type { ListResult, PackagePolicy } from '@kbn/fleet-plugin/common'; +import type { PackagePolicy } from '@kbn/fleet-plugin/common'; import type { Artifact, PackagePolicyClient } from '@kbn/fleet-plugin/server'; import type { ExceptionListClient } from '@kbn/lists-plugin/server'; import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; import { AppFeatureKey } from '@kbn/security-solution-features/keys'; +import { stringify } from '../../../utils/stringify'; +import { QueueProcessor } from '../../../utils/queue_processor'; import type { AppFeaturesService } from '../../../../lib/app_features_service/app_features_service'; import type { ExperimentalFeatures } from '../../../../../common'; import type { ManifestSchemaVersion } from '../../../../../common/endpoint/schema/common'; @@ -70,24 +72,6 @@ const iterateArtifactsBuildResult = ( } }; -const iterateAllListItems = async ( - pageSupplier: (page: number, perPage: number) => Promise>, - itemCallback: (items: T[]) => void -) => { - let paging = true; - let page = 1; - const perPage = 1000; - - while (paging) { - const { items, total } = await pageSupplier(page, perPage); - - itemCallback(items); - - paging = (page - 1) * perPage + items.length < total; - page++; - } -}; - export interface ManifestManagerContext { savedObjectsClient: SavedObjectsClientContract; artifactClient: EndpointArtifactClientInterface; @@ -407,7 +391,7 @@ export class ManifestManager { } /** - * Writes new artifact SOs. + * Writes new artifact to Fleet * * @param artifacts An InternalArtifactCompleteSchema array representing the artifacts. * @param newManifest A Manifest representing the new manifest @@ -418,7 +402,6 @@ export class ManifestManager { newManifest: Manifest ): Promise { const errors: Error[] = []; - const artifactsToCreate: InternalArtifactCompleteSchema[] = []; for (const artifact of artifacts) { @@ -433,28 +416,58 @@ export class ManifestManager { return errors; } + this.logger.debug(`Creating [${artifactsToCreate.length}] artifacts`); + const { artifacts: fleetArtifacts, errors: createErrors } = await this.artifactClient.bulkCreateArtifacts(artifactsToCreate); + this.logger.info(`Count of artifacts created: ${fleetArtifacts?.length ?? 0}`); + if (createErrors) { errors.push(...createErrors); } + const newArtifactsAddedToManifest: string[] = []; + const artifactsNotCreated: string[] = []; + if (fleetArtifacts) { - const fleetArtfactsByIdentifier: { [key: string]: InternalArtifactCompleteSchema } = {}; + const fleetArtifactsByIdentifier: { [key: string]: InternalArtifactCompleteSchema } = {}; + fleetArtifacts.forEach((fleetArtifact) => { - fleetArtfactsByIdentifier[getArtifactId(fleetArtifact)] = fleetArtifact; + fleetArtifactsByIdentifier[getArtifactId(fleetArtifact)] = fleetArtifact; }); + artifactsToCreate.forEach((artifact) => { const artifactId = getArtifactId(artifact); - const fleetArtifact = fleetArtfactsByIdentifier[artifactId]; + const fleetArtifact = fleetArtifactsByIdentifier[artifactId]; + + if (!fleetArtifact) { + artifactsNotCreated.push(artifactId); + + return; + } - if (!fleetArtifact) return; newManifest.replaceArtifact(fleetArtifact); - this.logger.debug(`New created artifact ${artifactId} added to the manifest`); + newArtifactsAddedToManifest.push(artifactId); }); } + if (artifactsNotCreated.length) { + this.logger.debug( + `A total of [${ + artifactsNotCreated.length + }] artifacts were not created. Prior version of the artifact will remain in manifest.\n${artifactsNotCreated.join( + '\n' + )}` + ); + } + + if (newArtifactsAddedToManifest.length !== 0) { + this.logger.debug( + `Newly created artifacts added to the manifest:\n${newArtifactsAddedToManifest.join('\n')}` + ); + } + return errors; } @@ -469,15 +482,24 @@ export class ManifestManager { if (isEmpty(artifactIds)) { return []; } + const errors = await this.artifactClient.bulkDeleteArtifacts(artifactIds); + if (!isEmpty(errors)) { return errors; } - for (const artifactId of artifactIds) { - this.logger.info(`Cleaned up artifact ${artifactId}`); + + this.logger.info(`Count of cleaned up artifacts: ${artifactIds.length}`); + + if (artifactIds.length !== 0) { + this.logger.debug(`Deleted artifacts from cleanup:\n${artifactIds.join('\n ')}`); } + return []; } catch (err) { + this.logger.error( + `Attempted to delete [${artifactIds.length}] outdated artifacts failed with: ${err.message}\n${err.stack}` + ); return [err]; } } @@ -508,22 +530,35 @@ export class ManifestManager { const fleetArtifacts = await this.listAllArtifacts(); const fleetArtifactsById = keyBy(fleetArtifacts, (artifact) => getArtifactId(artifact)); + const invalidArtifactIds: string[] = []; + // Ensure that all artifacts currently defined in the Manifest have a valid artifact in fleet, + // and remove any that does not have an actual artifact from the manifest for (const entry of manifestSo.attributes.artifacts) { const artifact = fleetArtifactsById[entry.artifactId]; if (!artifact) { - this.logger.error( - new InvalidInternalManifestError(`artifact id [${entry.artifactId}] not found!`, { - entry, - action: 'removed from internal ManifestManger tracking map', - }) - ); + invalidArtifactIds.push(entry.artifactId); } else { manifest.addEntry(artifact, entry.policyId); } } + if (invalidArtifactIds.length) { + this.logger.warn( + `Missing artifacts detected! Internal artifact manifest (SavedObject version [${ + manifestSo.version + }]) references [${ + invalidArtifactIds.length + }] artifact IDs that don't exist.\nFirst 10 below (run with logging set to 'debug' to see all):\n${invalidArtifactIds + .slice(0, 10) + .join('\n')}` + ); + this.logger.debug( + `Artifact ID references that are missing:\n${stringify(invalidArtifactIds)}` + ); + } + return manifest; } catch (error) { if (!error.output || error.output.statusCode !== 404) { @@ -569,15 +604,10 @@ export class ManifestManager { for (const result of results) { iterateArtifactsBuildResult(result, (artifact, policyId) => { - const artifactToAdd = baselineManifest.getArtifact(getArtifactId(artifact)) || artifact; - if (!internalArtifactCompleteSchema.is(artifactToAdd)) { - throw new EndpointError( - `Incomplete artifact detected: ${getArtifactId(artifactToAdd)}`, - artifactToAdd - ); - } - - manifest.addEntry(artifactToAdd, policyId); + manifest.addEntry( + baselineManifest.getArtifact(getArtifactId(artifact)) || artifact, + policyId + ); }); } @@ -592,81 +622,93 @@ export class ManifestManager { * @returns {Promise} Any errors encountered. */ public async tryDispatch(manifest: Manifest): Promise { - const allPackagePolicies: PackagePolicy[] = []; - await iterateAllListItems( - (page, perPage) => this.listEndpointPolicies(page, perPage), - (packagePoliciesBatch) => { - allPackagePolicies.push(...packagePoliciesBatch); - } - ); + const errors: Error[] = []; + const updatedPolicies: string[] = []; + const unChangedPolicies: string[] = []; + const manifestVersion = manifest.getSemanticVersion(); + const execId = Math.random().toString(32).substring(3, 8); + const policyUpdateBatchProcessor = new QueueProcessor({ + batchSize: this.packagerTaskPackagePolicyUpdateBatchSize, + logger: this.logger, + key: `tryDispatch.${execId}`, + batchHandler: async ({ data: currentBatch }) => { + const response = await this.packagePolicyService.bulkUpdate( + this.savedObjectsClient, + this.esClient, + currentBatch + ); - const packagePoliciesToUpdate: PackagePolicy[] = []; + if (!isEmpty(response.failedPolicies)) { + errors.push( + ...response.failedPolicies.map((failedPolicy) => { + if (failedPolicy.error instanceof Error) { + return failedPolicy.error; + } else { + return new Error(failedPolicy.error.message); + } + }) + ); + } - const errors: Error[] = []; - allPackagePolicies.forEach((packagePolicy) => { - const { id } = packagePolicy; - if (packagePolicy.inputs.length > 0 && packagePolicy.inputs[0].config !== undefined) { - const oldManifest = packagePolicy.inputs[0].config.artifact_manifest ?? { - value: {}, - }; - - const newManifestVersion = manifest.getSemanticVersion(); - if (semver.gt(newManifestVersion, oldManifest.value.manifest_version)) { - const serializedManifest = manifest.toPackagePolicyManifest(id); - - if (!manifestDispatchSchema.is(serializedManifest)) { - errors.push(new EndpointError(`Invalid manifest for policy ${id}`, serializedManifest)); - } else if (!manifestsEqual(serializedManifest, oldManifest.value)) { - packagePolicy.inputs[0].config.artifact_manifest = { value: serializedManifest }; - packagePoliciesToUpdate.push(packagePolicy); + if (response.updatedPolicies) { + updatedPolicies.push( + ...response.updatedPolicies.map((policy) => { + return `[${policy.id}][${policy.name}] updated with manifest version: [${manifestVersion}]`; + }) + ); + } + }, + }); + + for await (const policies of this.fetchAllPolicies()) { + for (const packagePolicy of policies) { + const { id, name } = packagePolicy; + + if (packagePolicy.inputs.length > 0 && packagePolicy.inputs[0].config !== undefined) { + const oldManifest = packagePolicy.inputs[0].config.artifact_manifest ?? { + value: {}, + }; + + const newManifestVersion = manifest.getSemanticVersion(); + + if (semver.gt(newManifestVersion, oldManifest.value.manifest_version)) { + const serializedManifest = manifest.toPackagePolicyManifest(id); + + if (!manifestDispatchSchema.is(serializedManifest)) { + errors.push( + new EndpointError(`Invalid manifest for policy ${id}`, serializedManifest) + ); + } else if (!manifestsEqual(serializedManifest, oldManifest.value)) { + packagePolicy.inputs[0].config.artifact_manifest = { value: serializedManifest }; + policyUpdateBatchProcessor.addToQueue(packagePolicy); + } else { + unChangedPolicies.push(`[${id}][${name}] No change in manifest content`); + } } else { - this.logger.debug( - `No change in manifest content for package policy: ${id}. Staying on old version` - ); + unChangedPolicies.push(`[${id}][${name}] No change in manifest version`); } } else { - this.logger.debug(`No change in manifest version for package policy: ${id}`); + errors.push( + new EndpointError(`Package Policy ${id} has no 'inputs[0].config'`, packagePolicy) + ); } - } else { - errors.push( - new EndpointError(`Package Policy ${id} has no 'inputs[0].config'`, packagePolicy) - ); } - }); + } - // Split updates in batches with batch size: packagerTaskPackagePolicyUpdateBatchSize - const updateBatches = chunk( - packagePoliciesToUpdate, - this.packagerTaskPackagePolicyUpdateBatchSize + await policyUpdateBatchProcessor.complete(); + + this.logger.info( + `Processed [${updatedPolicies.length + unChangedPolicies.length}] Policies: updated: [${ + updatedPolicies.length + }], un-changed: [${unChangedPolicies.length}]` ); - for (const currentBatch of updateBatches) { - const response = await this.packagePolicyService.bulkUpdate( - this.savedObjectsClient, - this.esClient, - currentBatch - ); + if (updatedPolicies.length) { + this.logger.debug(`Updated Policies:\n ${updatedPolicies.join('\n ')}`); + } - // Update errors - if (!isEmpty(response.failedPolicies)) { - errors.push( - ...response.failedPolicies.map((failedPolicy) => { - if (failedPolicy.error instanceof Error) { - return failedPolicy.error; - } else { - return new Error(failedPolicy.error.message); - } - }) - ); - } - // Log success updates - for (const updatedPolicy of response.updatedPolicies || []) { - this.logger.debug( - `Updated package policy ${ - updatedPolicy.id - } with manifest version ${manifest.getSemanticVersion()}` - ); - } + if (unChangedPolicies.length) { + this.logger.debug(`Un-changed Policies:\n ${unChangedPolicies.join('\n ')}`); } return errors; @@ -696,31 +738,24 @@ export class ManifestManager { this.logger.info(`Committed manifest ${manifest.getSemanticVersion()}`); } - private async listEndpointPolicies( - page: number, - perPage: number - ): Promise> { - return this.packagePolicyService.list(this.savedObjectsClient, { - page, - perPage, + private fetchAllPolicies(): AsyncIterable { + return this.packagePolicyService.fetchAllItems(this.savedObjectsClient, { kuery: 'ingest-package-policies.package.name:endpoint', }); } private async listEndpointPolicyIds(): Promise { const allPolicyIds: string[] = []; - await iterateAllListItems( - (page, perPage) => { - return this.packagePolicyService.listIds(this.savedObjectsClient, { - page, - perPage, - kuery: 'ingest-package-policies.package.name:endpoint', - }); - }, - (packagePolicyIdsBatch) => { - allPolicyIds.push(...packagePolicyIdsBatch); - } - ); + const idFetcher = this.packagePolicyService.fetchAllItemIds(this.savedObjectsClient, { + kuery: 'ingest-package-policies.package.name:endpoint', + }); + + for await (const itemIds of idFetcher) { + allPolicyIds.push(...itemIds); + } + + this.logger.debug(`Retrieved [${allPolicyIds.length}] endpoint integration policy IDs`); + return allPolicyIds; } @@ -733,70 +768,68 @@ export class ManifestManager { * @returns Artifact[] */ private async listAllArtifacts(): Promise { - const fleetArtifacts = []; - const perPage = 100; - let page = 1; + const fleetArtifacts: Artifact[] = []; + let total = 0; - let fleetArtifactsResponse = await this.artifactClient.listArtifacts({ - perPage, - page, - }); - fleetArtifacts.push(...fleetArtifactsResponse.items); - - while ( - fleetArtifactsResponse.total > fleetArtifacts.length && - !isEmpty(fleetArtifactsResponse.items) - ) { - page += 1; - fleetArtifactsResponse = await this.artifactClient.listArtifacts({ - perPage, - page, - }); - fleetArtifacts.push(...fleetArtifactsResponse.items); + for await (const artifacts of this.artifactClient.fetchAll()) { + fleetArtifacts.push(...artifacts); + total += artifacts.length; } + + this.logger.info(`Count of current stored artifacts: ${total}`); + return fleetArtifacts; } /** - * Cleanup .fleet-artifacts index if there are some orphan artifacts + * Pulls in all artifacts from Fleet and checks to ensure they are all being referenced + * by the Manifest. If any are found to not be in the current Manifest (orphan), they + * are cleaned up (deleted) */ public async cleanup(manifest: Manifest) { - try { - const fleetArtifacts = await this.listAllArtifacts(); - if (isEmpty(fleetArtifacts)) { - return; - } - - const badArtifacts = []; - const badArtifactIds = []; + const badArtifactIds: string[] = []; + const errors: string[] = []; + const artifactDeletionProcess = new QueueProcessor({ + batchSize: this.packagerTaskPackagePolicyUpdateBatchSize, + logger: this.logger, + key: 'cleanup', + batchHandler: async ({ batch, data }) => { + const deleteErrors = await this.artifactClient.bulkDeleteArtifacts(data); + + badArtifactIds.push(...data); + + if (deleteErrors.length) { + errors.push( + `Delete batch #[${batch}] with [${data.length}] items:\n${stringify(deleteErrors)}` + ); + } + }, + }); - const manifestArtifactsIds = manifest - .getAllArtifacts() - .map((artifact) => getArtifactId(artifact)); + const validArtifactIds = manifest.getAllArtifacts().map((artifact) => getArtifactId(artifact)); - for (const fleetArtifact of fleetArtifacts) { - const artifactId = getArtifactId(fleetArtifact); - const isArtifactInManifest = manifestArtifactsIds.includes(artifactId); + for await (const artifacts of this.artifactClient.fetchAll()) { + for (const artifact of artifacts) { + const artifactId = getArtifactId(artifact); + const isArtifactInManifest = validArtifactIds.includes(artifactId); if (!isArtifactInManifest) { - badArtifacts.push(fleetArtifact); - badArtifactIds.push(artifactId); + artifactDeletionProcess.addToQueue(artifactId); } } + } - if (isEmpty(badArtifacts)) { - return; - } + await artifactDeletionProcess.complete(); + if (errors.length > 0) { this.logger.error( - new EndpointError(`Cleaning up ${badArtifacts.length} orphan artifacts`, badArtifacts) + `The following errors were encountered while attempting to delete [${ + badArtifactIds.length + }] orphaned artifacts:\n${stringify(errors)}` ); - - await this.artifactClient.bulkDeleteArtifacts(badArtifactIds); - - this.logger.info(`All orphan artifacts has been removed successfully`); - } catch (error) { - this.logger.error(new EndpointError('There was an error cleaning orphan artifacts', error)); + } else if (badArtifactIds.length > 0) { + this.logger.info(`Count of orphan artifacts cleaned up: ${badArtifactIds.length}`); + this.logger.debug(`Orphan artifacts deleted from Fleet:\n${stringify(badArtifactIds)}`); } } } diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/mocks.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/mocks.ts index 2acfa7b7b4794..1a1dd701e9803 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/mocks.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/mocks.ts @@ -65,6 +65,7 @@ export const createEndpointArtifactClientMock = ( bulkDeleteArtifacts: jest.fn(async (...args) => endpointArtifactClientMocked.bulkDeleteArtifacts(...args) ), + fetchAll: jest.fn((...args) => endpointArtifactClientMocked.fetchAll(...args)), _esClient: esClient, }; }; diff --git a/x-pack/plugins/security_solution/server/endpoint/utils/queue_processor.ts b/x-pack/plugins/security_solution/server/endpoint/utils/queue_processor.ts new file mode 100644 index 0000000000000..f4f3e4ac76852 --- /dev/null +++ b/x-pack/plugins/security_solution/server/endpoint/utils/queue_processor.ts @@ -0,0 +1,152 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Logger } from '@kbn/core/server'; + +export interface QueueProcessorOptions { + batchHandler: (batch: { batch: number; data: T[] }) => Promise; + batchSize?: number; + logger?: Logger; + /** + * Used when `logger` is passed. It will be used to define the logging messages context path. + * Defaults to the name of the callback provided in `batchHandler` + */ + key?: string; +} + +/** + * Process an un-bound amount of items in batches. Each batch is process once the queued reach the + * `batchSize`, thus processing is gradually executed ensuring that data is not held in memory + * for too long. Once all items are added to the Queue, calling + * `.complete()` will ensure they are all processed. + * + * @example + * const processor = new QueueProcessor<{ id: string }>({ + * batchHandler: ({ data, batch }) => { + * // data === array of `{ id: string }` + * // batch === batch number + * } + * }); + * + * const myIdList = [ .... ]; // Array with 50 string + * + * for (const id of myIdList) { + * batchHandler.addToQueue({ id: id}); + * } + * + * await processor.complete(); + */ +export class QueueProcessor { + private readonly batchSize: number; + private readonly batchHandler: QueueProcessorOptions['batchHandler']; + private readonly logger: Logger | undefined = undefined; + + private queue: T[] = []; + private processingPromise: Promise | undefined = undefined; + private batchCount = 0; + private itemsProcessedCount = 0; + + constructor({ + batchHandler, + batchSize = 10, + logger, + key = 'QueueProcessor', + }: QueueProcessorOptions) { + if (batchSize < 1 || !Number.isFinite(batchSize)) { + throw new Error(`batchSize must be a number greater than zero`); + } + + this.batchSize = batchSize; + this.batchHandler = batchHandler; + this.logger = logger ? logger.get(key) : undefined; + } + + protected log( + message: string, + output: keyof Pick = 'info' + ): void { + if (this.logger) { + this.logger[output](message); + } + } + + protected async processQueue(all: boolean = false) { + if (this.processingPromise || this.queue.length === 0) { + return; + } + + const runThroughQueue = async () => { + let hasMoreData = true; + + while (hasMoreData) { + try { + if (all || this.queue.length >= this.batchSize) { + const batchPage = this.queue.splice(0, this.batchSize); + const batchPageSize = batchPage.length; + const remainingItemsSize = this.queue.length; + + hasMoreData = (all && remainingItemsSize > 0) || remainingItemsSize >= this.batchSize; + this.itemsProcessedCount += batchPageSize; + this.batchCount++; + + try { + this.log( + `Processing batch [${this.batchCount}] with [${batchPageSize}] items. Items remaining in queue: [${remainingItemsSize}]`, + 'debug' + ); + await this.batchHandler({ batch: this.batchCount, data: batchPage }); + } catch (err) { + this.log( + `batchHandler threw error (below). Will continue on to next batch page:\n${err}`, + 'debug' + ); + // ignore errors in the batch page processing and keep going to process others. + // callback should have handled errors that its process might throw + } + } else { + hasMoreData = false; + } + } catch (err) { + hasMoreData = false; + throw err; + } + } + }; + + this.processingPromise = runThroughQueue().finally(() => { + this.processingPromise = undefined; + }); + + return this.processingPromise; + } + + /** + * Adds an update to the queue + */ + public addToQueue(...data: T[]) { + this.queue.push(...data); + this.processQueue(); + } + + /** + * Flushes the queue and awaits processing of all remaining updates. + * + * **IMPORTANT**: Always make sure `complete()` is called to ensure no items are left in the queue + */ + public async complete(): Promise { + if (this.processingPromise) { + await this.processingPromise.finally(() => {}); + } + + await this.processQueue(true); + + this.log( + `Processed [${this.batchCount}] batches and a total of [${this.itemsProcessedCount}] items`, + 'debug' + ); + } +} diff --git a/x-pack/plugins/security_solution/server/endpoint/utils/translations.ts b/x-pack/plugins/security_solution/server/endpoint/utils/translations.ts new file mode 100644 index 0000000000000..1b904bf49af15 --- /dev/null +++ b/x-pack/plugins/security_solution/server/endpoint/utils/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 EMPTY_COMMENT = i18n.translate( + 'xpack.securitySolution.endpoint.updateCases.emptyComment', + { + defaultMessage: 'No comment provided', + } +); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/legacy_rules_notification_rule_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/legacy_rules_notification_rule_type.ts index 5e237f8564559..8d91d37fd3672 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/legacy_rules_notification_rule_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/legacy_rules_notification_rule_type.ts @@ -47,6 +47,12 @@ export const legacyRulesNotificationRuleType = ({ validate: { params: legacyRulesNotificationParams, }, + schemas: { + params: { + type: 'config-schema', + schema: legacyRulesNotificationParams, + }, + }, useSavedObjectReferences: { extractReferences: (params) => legacyExtractReferences({ logger, params }), injectReferences: (params, savedObjectReferences) => 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 f45f005c7c34b..5abdc9b4904e4 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 @@ -181,11 +181,12 @@ describe('Create rule route', () => { // @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') => ({ + const getResponseAction = (command: string = 'isolate', config?: object) => ({ action_type_id: '.endpoint', params: { command, comment: '', + ...(config ? { config } : {}), }, }); const defaultAction = getResponseAction(); @@ -224,8 +225,38 @@ describe('Create rule route', () => { 'User is not authorized to change isolate response actions' ); }); + test('pass when provided with process action', async () => { + const processAction = getResponseAction('kill-process', { overwrite: true, field: '' }); + + const request = requestMock.create({ + method: 'post', + path: DETECTION_ENGINE_RULES_URL, + body: { + ...getCreateRulesSchemaMock(), + response_actions: [processAction], + }, + }); + const result = await server.validate(request); + expect(result.badRequest).not.toHaveBeenCalled(); + }); test('fails when provided with an unsupported command', async () => { - const wrongAction = getResponseAction('processes'); + const wrongAction = getResponseAction('execute'); + + const request = requestMock.create({ + method: 'post', + path: DETECTION_ENGINE_RULES_URL, + body: { + ...getCreateRulesSchemaMock(), + response_actions: [wrongAction], + }, + }); + const result = await server.validate(request); + expect(result.badRequest).toHaveBeenCalledWith( + `response_actions.0.action_type_id: Invalid literal value, expected \".osquery\", response_actions.0.params.command: Invalid literal value, expected \"isolate\", response_actions.0.params.command: Invalid enum value. Expected 'kill-process' | 'suspend-process', received 'execute', response_actions.0.params.config: Required` + ); + }); + test('fails when provided with payload missing data', async () => { + const wrongAction = getResponseAction('kill-process', { overwrite: true }); const request = requestMock.create({ method: 'post', @@ -237,7 +268,7 @@ describe('Create rule route', () => { }); const result = await server.validate(request); expect(result.badRequest).toHaveBeenCalledWith( - 'response_actions.0.action_type_id: Invalid literal value, expected ".osquery", response_actions.0.params.command: Invalid literal value, expected "isolate"' + `response_actions.0.action_type_id: Invalid literal value, expected \".osquery\", response_actions.0.params.command: Invalid literal value, expected \"isolate\", response_actions.0.params.config.field: Required` ); }); }); 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 6bdafa76fd9b6..4eea0dc1797d5 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 @@ -23,7 +23,7 @@ import { getUpdateRulesSchemaMock, } from '../../../../../../../common/api/detection_engine/model/rule_schema/mocks'; import { getQueryRuleParams } from '../../../../rule_schema/mocks'; -import { ResponseActionTypesEnum } from '../../../../../../../common/api/detection_engine/model/rule_response_actions'; +import { ResponseActionTypesEnum } from '../../../../../../../common/api/detection_engine'; jest.mock('../../../../../machine_learning/authz'); @@ -189,11 +189,12 @@ describe('Update rule route', () => { // @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') => ({ + const getResponseAction = (command: string = 'isolate', config?: object) => ({ action_type_id: '.endpoint', params: { command, comment: '', + ...(config ? { config } : {}), }, }); const defaultAction = getResponseAction(); @@ -249,6 +250,7 @@ describe('Update rule route', () => { params: { command: 'isolate', comment: '', + config: undefined, }, }, ], @@ -272,7 +274,7 @@ describe('Update rule route', () => { ); }); test('fails when provided with an unsupported command', async () => { - const wrongAction = getResponseAction('processes'); + const wrongAction = getResponseAction('execute'); const request = requestMock.create({ method: 'post', @@ -284,7 +286,23 @@ describe('Update rule route', () => { }); const result = await server.validate(request); expect(result.badRequest).toHaveBeenCalledWith( - `response_actions.0.action_type_id: Invalid literal value, expected \".osquery\", response_actions.0.params.command: Invalid literal value, expected \"isolate\"` + `response_actions.0.action_type_id: Invalid literal value, expected \".osquery\", response_actions.0.params.command: Invalid literal value, expected \"isolate\", response_actions.0.params.command: Invalid enum value. Expected 'kill-process' | 'suspend-process', received 'execute', response_actions.0.params.config: Required` + ); + }); + test('fails when provided with payload missing data', async () => { + const wrongAction = getResponseAction('kill-process', { overwrite: true }); + + const request = requestMock.create({ + method: 'post', + path: DETECTION_ENGINE_RULES_URL, + body: { + ...getCreateRulesSchemaMock(), + response_actions: [wrongAction], + }, + }); + const result = await server.validate(request); + expect(result.badRequest).toHaveBeenCalledWith( + `response_actions.0.action_type_id: Invalid literal value, expected \".osquery\", response_actions.0.params.command: Invalid literal value, expected \"isolate\", response_actions.0.params.config.field: Required` ); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/endpoint_params_type_guards.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/endpoint_params_type_guards.ts new file mode 100644 index 0000000000000..d1dbb9c7d4958 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/endpoint_params_type_guards.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { + DefaultParams, + ProcessesParams, + RuleResponseEndpointAction, +} from '../../../../common/api/detection_engine'; + +export const isIsolateAction = ( + params: RuleResponseEndpointAction['params'] +): params is DefaultParams => { + return params.command === 'isolate'; +}; + +export const isProcessesAction = ( + params: RuleResponseEndpointAction['params'] +): params is ProcessesParams => { + return params.command === 'kill-process' || params.command === 'suspend-process'; +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/endpoint_response_action.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/endpoint_response_action.ts index f6c3161921934..b6aa53bb32ff6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/endpoint_response_action.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/endpoint_response_action.ts @@ -5,48 +5,81 @@ * 2.0. */ -import { each, map, uniq } from 'lodash'; import { ALERT_RULE_NAME, ALERT_RULE_UUID } from '@kbn/rule-data-utils'; -import type { ResponseActionAlerts } from './types'; +import { each } from 'lodash'; + +import type { ExperimentalFeatures } from '../../../../common'; +import { isIsolateAction, isProcessesAction } from './endpoint_params_type_guards'; +import type { RuleResponseEndpointAction } from '../../../../common/api/detection_engine'; import type { EndpointAppContextService } from '../../../endpoint/endpoint_app_context_services'; -import type { RuleResponseEndpointAction } from '../../../../common/api/detection_engine/model/rule_response_actions'; +import { getProcessAlerts, getIsolateAlerts, getErrorProcessAlerts } from './utils'; + +import type { ResponseActionAlerts, AlertsAction } from './types'; export const endpointResponseAction = ( responseAction: RuleResponseEndpointAction, endpointAppContextService: EndpointAppContextService, - { alerts }: ResponseActionAlerts + { alerts }: ResponseActionAlerts, + experimentalFeatures: ExperimentalFeatures ) => { const { comment, command } = responseAction.params; + const commonData = { comment, command, rule_id: alerts[0][ALERT_RULE_UUID], rule_name: alerts[0][ALERT_RULE_NAME], + agent_type: 'endpoint' as const, }; - const agentIds = uniq(map(alerts, 'agent.id')); - const alertIds = map(alerts, '_id'); - const hosts = alerts.reduce>((acc, alert) => { - if (alert.agent?.name && !acc[alert.agent.id]) { - acc[alert.agent.id] = alert.agent.name; - } - return acc; - }, {}); - return Promise.all( - each(agentIds, async (agent) => - endpointAppContextService.getActionCreateService().createActionFromAlert( + if (isIsolateAction(responseAction.params)) { + const alertsPerAgent = getIsolateAlerts(alerts); + each(alertsPerAgent, (actionPayload) => { + return endpointAppContextService.getActionCreateService().createActionFromAlert( { - hosts: { - [agent]: { - name: hosts[agent], - }, - }, - endpoint_ids: [agent], - alert_ids: alertIds, + ...actionPayload, ...commonData, }, - [agent] - ) - ) - ); + actionPayload.endpoint_ids + ); + }); + } + + const automatedProcessActionsEnabled = experimentalFeatures?.automatedProcessActionsEnabled; + + if (automatedProcessActionsEnabled) { + const createProcessActionFromAlerts = ( + actionAlerts: Record> + ) => { + const createAction = async (alert: AlertsAction) => { + const { hosts, parameters, error } = alert; + + const actionData = { + hosts, + endpoint_ids: alert.endpoint_ids, + alert_ids: alert.alert_ids, + error, + parameters, + ...commonData, + }; + + return endpointAppContextService + .getActionCreateService() + .createActionFromAlert(actionData, alert.endpoint_ids); + }; + return each(actionAlerts, (actionPerAgent) => { + return each(actionPerAgent, createAction); + }); + }; + + if (isProcessesAction(responseAction.params)) { + const foundFields = getProcessAlerts(alerts, responseAction.params.config); + const notFoundField = getErrorProcessAlerts(alerts, responseAction.params.config); + + const processActions = createProcessActionFromAlerts(foundFields); + const processActionsWithError = createProcessActionFromAlerts(notFoundField); + + return Promise.all([processActions, processActionsWithError]); + } + } }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/schedule_notification_response_actions.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/schedule_notification_response_actions.test.ts index 672434bfc94d0..927ba3cbe4a46 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/schedule_notification_response_actions.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/schedule_notification_response_actions.test.ts @@ -6,105 +6,213 @@ */ import { getScheduleNotificationResponseActionsService } from './schedule_notification_response_actions'; -import type { RuleResponseAction } from '../../../../common/api/detection_engine/model/rule_response_actions'; -import { ResponseActionTypesEnum } from '../../../../common/api/detection_engine/model/rule_response_actions'; +import type { RuleResponseAction } from '../../../../common/api/detection_engine'; +import { ResponseActionTypesEnum } from '../../../../common/api/detection_engine'; +import { ALERT_RULE_NAME, ALERT_RULE_UUID } from '@kbn/rule-data-utils'; describe('ScheduleNotificationResponseActions', () => { - const signalOne = { agent: { id: 'agent-id-1' }, _id: 'alert-id-1', user: { id: 'S-1-5-20' } }; - const signalTwo = { agent: { id: 'agent-id-2' }, _id: 'alert-id-2' }; - const signals = [signalOne, signalTwo]; - const defaultQueryParams = { - ecsMapping: { testField: { field: 'testField', value: 'testValue' } }, - savedQueryId: 'testSavedQueryId', - query: undefined, - queries: [], - packId: undefined, - }; - const defaultPackParams = { - packId: 'testPackId', - queries: [], - query: undefined, - ecsMapping: { testField: { field: 'testField', value: 'testValue' } }, - savedQueryId: undefined, - }; - const defaultQueries = { - ecs_mapping: undefined, - platform: 'windows', - version: '1.0.0', - snapshot: true, - removed: false, + const signalOne = { + agent: { id: 'agent-id-1' }, + _id: 'alert-id-1', + user: { id: 'S-1-5-20' }, + process: { + pid: 123, + }, + [ALERT_RULE_UUID]: 'rule-id-1', + [ALERT_RULE_NAME]: 'rule-name-1', }; + const signalTwo = { agent: { id: 'agent-id-2' }, _id: 'alert-id-2' }; + const getSignals = () => [signalOne, signalTwo]; - const defaultResultParams = { - agent_ids: ['agent-id-1', 'agent-id-2'], - alert_ids: ['alert-id-1', 'alert-id-2'], - }; - const defaultQueryResultParams = { - ...defaultResultParams, - ecs_mapping: { testField: { field: 'testField', value: 'testValue' } }, - ecsMapping: undefined, - saved_query_id: 'testSavedQueryId', - savedQueryId: undefined, - queries: [], - }; - const defaultPackResultParams = { - ...defaultResultParams, - query: undefined, - saved_query_id: undefined, - ecs_mapping: { testField: { field: 'testField', value: 'testValue' } }, - }; const osqueryActionMock = { create: jest.fn(), stop: jest.fn(), }; - const endpointActionMock = jest.fn(); - + const endpointActionMock = { + getActionCreateService: jest.fn().mockReturnValue({ + createActionFromAlert: jest.fn(), + }), + }; const scheduleNotificationResponseActions = getScheduleNotificationResponseActionsService({ osqueryCreateActionService: osqueryActionMock, endpointAppContextService: endpointActionMock as never, + experimentalFeatures: { + automatedProcessActionsEnabled: true, + endpointResponseActionsEnabled: true, + } as never, }); - const simpleQuery = 'select * from uptime'; - it('should handle osquery response actions with query', async () => { - const responseActions: RuleResponseAction[] = [ - { - actionTypeId: ResponseActionTypesEnum['.osquery'], - params: { - ...defaultQueryParams, - query: simpleQuery, + describe('Osquery', () => { + const simpleQuery = 'select * from uptime'; + const defaultQueryParams = { + ecsMapping: { testField: { field: 'testField', value: 'testValue' } }, + savedQueryId: 'testSavedQueryId', + query: undefined, + queries: [], + packId: undefined, + }; + const defaultPackParams = { + packId: 'testPackId', + queries: [], + query: undefined, + ecsMapping: { testField: { field: 'testField', value: 'testValue' } }, + savedQueryId: undefined, + }; + const defaultQueries = { + ecs_mapping: undefined, + platform: 'windows', + version: '1.0.0', + snapshot: true, + removed: false, + }; + + const defaultResultParams = { + agent_ids: ['agent-id-1', 'agent-id-2'], + alert_ids: ['alert-id-1', 'alert-id-2'], + }; + const defaultQueryResultParams = { + ...defaultResultParams, + ecs_mapping: { testField: { field: 'testField', value: 'testValue' } }, + ecsMapping: undefined, + saved_query_id: 'testSavedQueryId', + savedQueryId: undefined, + queries: [], + }; + const defaultPackResultParams = { + ...defaultResultParams, + query: undefined, + saved_query_id: undefined, + ecs_mapping: { testField: { field: 'testField', value: 'testValue' } }, + }; + it('should handle osquery response actions with query', async () => { + const signals = getSignals(); + const responseActions: RuleResponseAction[] = [ + { + actionTypeId: ResponseActionTypesEnum['.osquery'], + params: { + ...defaultQueryParams, + query: simpleQuery, + }, }, - }, - ]; - scheduleNotificationResponseActions({ signals, responseActions }); + ]; + scheduleNotificationResponseActions({ signals, responseActions }); - expect(osqueryActionMock.create).toHaveBeenCalledWith({ - ...defaultQueryResultParams, - query: simpleQuery, + expect(osqueryActionMock.create).toHaveBeenCalledWith({ + ...defaultQueryResultParams, + query: simpleQuery, + }); + }); + + it('should handle osquery response actions with packs', async () => { + const signals = getSignals(); + + const responseActions: RuleResponseAction[] = [ + { + actionTypeId: ResponseActionTypesEnum['.osquery'], + params: { + ...defaultPackParams, + queries: [ + { + ...defaultQueries, + id: 'query-1', + query: simpleQuery, + }, + ], + packId: 'testPackId', + }, + }, + ]; + scheduleNotificationResponseActions({ signals, responseActions }); + + expect(osqueryActionMock.create).toHaveBeenCalledWith({ + ...defaultPackResultParams, + queries: [{ ...defaultQueries, id: 'query-1', query: simpleQuery }], + }); }); - // }); - it('should handle osquery response actions with packs', async () => { - const responseActions: RuleResponseAction[] = [ - { - actionTypeId: ResponseActionTypesEnum['.osquery'], - params: { - ...defaultPackParams, - queries: [ - { - ...defaultQueries, - id: 'query-1', - query: simpleQuery, + describe('Endpoint', () => { + it('should handle endpoint isolate actions', async () => { + const signals = getSignals(); + + const responseActions: RuleResponseAction[] = [ + { + actionTypeId: ResponseActionTypesEnum['.endpoint'], + params: { + command: 'isolate', + comment: 'test isolate comment', + }, + }, + ]; + scheduleNotificationResponseActions({ signals, responseActions }); + + expect( + endpointActionMock.getActionCreateService().createActionFromAlert + ).toHaveBeenCalledTimes(signals.length); + expect( + endpointActionMock.getActionCreateService().createActionFromAlert + ).toHaveBeenCalledWith( + { + alert_ids: ['alert-id-1'], + command: 'isolate', + comment: 'test isolate comment', + endpoint_ids: ['agent-id-1'], + agent_type: 'endpoint', + hosts: { + 'agent-id-1': { + id: 'agent-id-1', + name: '', }, - ], - packId: 'testPackId', + }, + rule_id: 'rule-id-1', + rule_name: 'rule-name-1', }, - }, - ]; - scheduleNotificationResponseActions({ signals, responseActions }); + ['agent-id-1'] + ); + }); + it('should handle endpoint kill-process actions', async () => { + const signals = getSignals(); + const responseActions: RuleResponseAction[] = [ + { + actionTypeId: ResponseActionTypesEnum['.endpoint'], + params: { + command: 'kill-process', + comment: 'test process comment', + config: { + overwrite: true, + field: '', + }, + }, + }, + ]; + scheduleNotificationResponseActions({ + signals, + responseActions, + }); - expect(osqueryActionMock.create).toHaveBeenCalledWith({ - ...defaultPackResultParams, - queries: [{ ...defaultQueries, id: 'query-1', query: simpleQuery }], + expect( + endpointActionMock.getActionCreateService().createActionFromAlert + ).toHaveBeenCalledWith( + { + agent_type: 'endpoint', + alert_ids: ['alert-id-1'], + command: 'kill-process', + comment: 'test process comment', + endpoint_ids: ['agent-id-1'], + error: undefined, + hosts: { + 'agent-id-1': { + id: 'agent-id-1', + name: undefined, + }, + }, + parameters: { + pid: 123, + }, + rule_id: 'rule-id-1', + rule_name: 'rule-name-1', + }, + ['agent-id-1'] + ); }); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/schedule_notification_response_actions.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/schedule_notification_response_actions.ts index a02fafe69d8f6..f05df335c7424 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/schedule_notification_response_actions.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/schedule_notification_response_actions.ts @@ -6,23 +6,26 @@ */ import { each } from 'lodash'; +import type { ExperimentalFeatures } from '../../../../common'; import type { EndpointAppContextService } from '../../../endpoint/endpoint_app_context_services'; import type { SetupPlugins } from '../../../plugin_contract'; import { ResponseActionTypesEnum } from '../../../../common/api/detection_engine/model/rule_response_actions'; import { osqueryResponseAction } from './osquery_response_action'; import { endpointResponseAction } from './endpoint_response_action'; import type { ScheduleNotificationActions } from '../rule_types/types'; -import type { Alert, AlertWithAgent } from './types'; +import type { AlertWithAgent, Alert } from './types'; interface ScheduleNotificationResponseActionsService { endpointAppContextService: EndpointAppContextService; osqueryCreateActionService?: SetupPlugins['osquery']['createActionService']; + experimentalFeatures: ExperimentalFeatures; } export const getScheduleNotificationResponseActionsService = ({ osqueryCreateActionService, endpointAppContextService, + experimentalFeatures, }: ScheduleNotificationResponseActionsService) => ({ signals, responseActions }: ScheduleNotificationActions) => { const alerts = (signals as Alert[]).filter((alert) => alert.agent?.id) as AlertWithAgent[]; @@ -37,9 +40,14 @@ export const getScheduleNotificationResponseActionsService = }); } if (responseAction.actionTypeId === ResponseActionTypesEnum['.endpoint']) { - endpointResponseAction(responseAction, endpointAppContextService, { - alerts, - }); + endpointResponseAction( + responseAction, + endpointAppContextService, + { + alerts, + }, + experimentalFeatures + ); } }); }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/types.ts index 5f1cee8d2a9ba..1ab3b8018bf1d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/types.ts @@ -6,11 +6,17 @@ */ import type { ParsedTechnicalFields } from '@kbn/rule-registry-plugin/common'; +import type { CreateActionPayload } from '../../../endpoint/services/actions/create/types'; export type Alert = ParsedTechnicalFields & { _id: string; agent?: AlertAgent; - process?: { pid: string }; + host?: { + name: string; + }; + process?: { + pid: string; + }; }; export interface AlertAgent { @@ -25,3 +31,8 @@ export interface AlertWithAgent extends Alert { export interface ResponseActionAlerts { alerts: AlertWithAgent[]; } + +export type AlertsAction = Pick< + CreateActionPayload, + 'alert_ids' | 'endpoint_ids' | 'hosts' | 'parameters' | 'error' +>; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/utils.test.ts new file mode 100644 index 0000000000000..0cb3962ab35fc --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/utils.test.ts @@ -0,0 +1,242 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getErrorProcessAlerts, getIsolateAlerts, getProcessAlerts } from './utils'; +import type { AlertWithAgent } from './types'; + +const getSampleAlerts = (): AlertWithAgent[] => { + const alert = { + _id: 'alert1', + process: { + pid: 1, + }, + agent: { + name: 'jammy-1', + id: 'agent-id-1', + }, + }; + const alert2 = { + _id: 'alert2', + process: { + pid: 2, + }, + agent: { + name: 'jammy-2', + id: 'agent-id-2', + }, + }; + const alert3 = { + _id: 'alert3', + process: { + pid: 2, + }, + agent: { + name: 'jammy-1', + id: 'agent-id-1', + }, + }; + const alert4 = { + _id: 'alert4', + agent: { + name: 'jammy-1', + id: 'agent-id-1', + }, + }; + const alert5 = { + _id: 'alert5', + process: { + entity_id: 2, + }, + agent: { + name: 'jammy-1', + id: 'agent-id-1', + }, + }; + // Casted as unknown first because we do not need all the data to test the functionality + return [alert, alert2, alert3, alert4, alert5] as unknown as AlertWithAgent[]; +}; +describe('EndpointResponseActionsUtils', () => { + describe('getIsolateAlerts', () => { + const alerts = getSampleAlerts(); + it('should return proper number of actions divided per agents with specified alert_ids', async () => { + const isolateAlerts = getIsolateAlerts(alerts); + + const result = { + 'agent-id-1': { + alert_ids: ['alert1', 'alert3', 'alert4', 'alert5'], + endpoint_ids: ['agent-id-1'], + hosts: { + 'agent-id-1': { + id: 'agent-id-1', + name: 'jammy-1', + }, + }, + }, + 'agent-id-2': { + alert_ids: ['alert2'], + endpoint_ids: ['agent-id-2'], + hosts: { + 'agent-id-2': { + id: 'agent-id-2', + name: 'jammy-2', + }, + }, + }, + }; + expect(isolateAlerts).toEqual(result); + }); + }); + describe('getProcessAlerts', () => { + const alerts = getSampleAlerts(); + + it('should return actions that are valid based on default field (pid)', async () => { + const processAlerts = getProcessAlerts(alerts, { + overwrite: true, + field: '', + }); + + const result = { + 'agent-id-1': { + '1': { + alert_ids: ['alert1'], + endpoint_ids: ['agent-id-1'], + hosts: { + 'agent-id-1': { + id: 'agent-id-1', + name: 'jammy-1', + }, + }, + parameters: { + pid: 1, + }, + }, + '2': { + alert_ids: ['alert3'], + endpoint_ids: ['agent-id-1'], + hosts: { + 'agent-id-1': { + id: 'agent-id-1', + name: 'jammy-1', + }, + }, + parameters: { + pid: 2, + }, + }, + }, + 'agent-id-2': { + '2': { + alert_ids: ['alert2'], + endpoint_ids: ['agent-id-2'], + hosts: { + 'agent-id-2': { + id: 'agent-id-2', + name: 'jammy-2', + }, + }, + parameters: { + pid: 2, + }, + }, + }, + }; + expect(processAlerts).toEqual(result); + }); + + it('should return actions that do not have value from default field (pid)', async () => { + const processAlerts = getProcessAlerts(alerts, { + overwrite: false, + field: 'process.entity_id', + }); + + const result = { + 'agent-id-1': { + '2': { + alert_ids: ['alert5'], + endpoint_ids: ['agent-id-1'], + hosts: { + 'agent-id-1': { + id: 'agent-id-1', + name: 'jammy-1', + }, + }, + parameters: { + entity_id: 2, + }, + }, + }, + }; + expect(processAlerts).toEqual(result); + }); + }); + describe('getErrorProcessAlerts', () => { + const alerts = getSampleAlerts(); + + it('should return actions that do not have value from default field (pid)', async () => { + const processAlerts = getErrorProcessAlerts(alerts, { + overwrite: true, + field: '', + }); + + const result = { + 'agent-id-1': { + 'process.pid': { + alert_ids: ['alert4', 'alert5'], + endpoint_ids: ['agent-id-1'], + error: 'process.pid', + hosts: { + 'agent-id-1': { + id: 'agent-id-1', + name: 'jammy-1', + }, + }, + parameters: {}, + }, + }, + }; + expect(processAlerts).toEqual(result); + }); + it('should return actions that do not have value from custom field name', async () => { + const processAlerts = getErrorProcessAlerts(alerts, { + overwrite: false, + field: 'process.entity_id', + }); + + const result = { + 'agent-id-1': { + 'process.entity_id': { + alert_ids: ['alert1', 'alert3', 'alert4'], + endpoint_ids: ['agent-id-1'], + error: 'process.entity_id', + hosts: { + 'agent-id-1': { + id: 'agent-id-1', + name: 'jammy-1', + }, + }, + parameters: {}, + }, + }, + 'agent-id-2': { + 'process.entity_id': { + alert_ids: ['alert2'], + endpoint_ids: ['agent-id-2'], + error: 'process.entity_id', + hosts: { + 'agent-id-2': { + id: 'agent-id-2', + name: 'jammy-2', + }, + }, + parameters: {}, + }, + }, + }; + expect(processAlerts).toEqual(result); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/utils.ts new file mode 100644 index 0000000000000..8d0c5c3ef74c9 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/utils.ts @@ -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 { get } from 'lodash'; +import type { AlertAgent, AlertWithAgent, AlertsAction } from './types'; +import type { ProcessesParams } from '../../../../common/api/detection_engine'; + +interface ProcessAlertsAcc { + [key: string]: Record; +} + +export const getProcessAlerts = ( + alerts: AlertWithAgent[], + config: ProcessesParams['config'] +): ProcessAlertsAcc => { + if (!config) { + return {}; + } + const { overwrite, field } = config; + + return alerts.reduce((acc: ProcessAlertsAcc, alert) => { + const valueFromAlert: number = overwrite ? alert.process?.pid : get(alert, field); + + if (valueFromAlert) { + const isEntityId = !overwrite && field.includes('entity_id'); + const paramKey = isEntityId ? 'entity_id' : 'pid'; + const { _id, agent } = alert; + const { id: agentId, name } = agent as AlertAgent; + const hostName = alert.host?.name; + + const currentAgent = acc[agentId]; + const currentValue = currentAgent?.[valueFromAlert]; + + return { + ...acc, + [agentId]: { + ...(currentAgent || {}), + [valueFromAlert]: { + ...(currentValue || {}), + alert_ids: [...(currentValue?.alert_ids || []), _id], + parameters: { [paramKey]: valueFromAlert }, + endpoint_ids: [agentId], + hosts: { + ...currentValue?.hosts, + [agentId]: { name: name || hostName, id: agentId }, + }, + }, + }, + }; + } + return acc; + }, {}); +}; + +export const getErrorProcessAlerts = ( + alerts: AlertWithAgent[], + config: ProcessesParams['config'] +): ProcessAlertsAcc => { + if (!config) { + return {}; + } + const { overwrite, field } = config; + + return alerts.reduce((acc: ProcessAlertsAcc, alert) => { + const valueFromAlert: number = overwrite ? alert.process?.pid : get(alert, field); + + if (!valueFromAlert) { + const { _id, agent } = alert; + const { id: agentId, name } = agent as AlertAgent; + const hostName = alert.host?.name; + + const errorField = overwrite ? 'process.pid' : field; + const currentAgent = acc[agentId]; + const currentValue = currentAgent?.[errorField]; + + return { + ...acc, + [agentId]: { + ...(currentAgent || {}), + [errorField]: { + ...(currentValue || {}), + alert_ids: [...(currentValue?.alert_ids || []), _id], + parameters: {}, + endpoint_ids: [agentId], + hosts: { + ...currentValue?.hosts, + [agentId]: { name: name || hostName || '', id: agentId }, + }, + error: errorField, + }, + }, + }; + } + return acc; + }, {}); +}; + +export const getIsolateAlerts = (alerts: AlertWithAgent[]): Record => + alerts.reduce((acc: Record, alert) => { + const { id: agentId, name: agentName } = alert.agent || {}; + + const hostName = alert.host?.name; + + return { + ...acc, + [agentId]: { + ...(acc?.[agentId] || {}), + hosts: { + ...(acc[agentId]?.hosts || {}), + [agentId]: { + name: agentName || hostName || '', + id: agentId, + }, + }, + endpoint_ids: [agentId], + alert_ids: [...(acc[agentId]?.alert_ids || []), alert._id], + }, + }; + }, {}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/create_eql_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/create_eql_alert_type.ts index c457afeb48a92..4674774f28d9a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/create_eql_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/create_eql_alert_type.ts @@ -39,6 +39,9 @@ export const createEqlAlertType = ( }, }, }, + schemas: { + params: { type: 'zod', schema: EqlRuleParams }, + }, actionGroups: [ { id: 'default', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/esql/create_esql_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/esql/create_esql_alert_type.ts index 5672190d3ec00..15ff7adb11013 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/esql/create_esql_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/esql/create_esql_alert_type.ts @@ -27,6 +27,9 @@ export const createEsqlAlertType = ( }, }, }, + schemas: { + params: { type: 'zod', schema: EsqlRuleParams }, + }, actionGroups: [ { id: 'default', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/create_indicator_match_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/create_indicator_match_alert_type.ts index 0b073d6c6b710..75d63f149e68a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/create_indicator_match_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/create_indicator_match_alert_type.ts @@ -43,6 +43,9 @@ export const createIndicatorMatchAlertType = ( }, }, }, + schemas: { + params: { type: 'zod', schema: ThreatRuleParams }, + }, actionGroups: [ { id: 'default', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/ml/create_ml_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/ml/create_ml_alert_type.ts index 1fb9527b04b80..ca0edac6fca4e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/ml/create_ml_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/ml/create_ml_alert_type.ts @@ -28,6 +28,9 @@ export const createMlAlertType = ( }, }, }, + schemas: { + params: { type: 'zod', schema: MachineLearningRuleParams }, + }, actionGroups: [ { id: 'default', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/create_new_terms_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/create_new_terms_alert_type.ts index 57a1ecfe38109..398e73d6861f8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/create_new_terms_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/create_new_terms_alert_type.ts @@ -69,6 +69,9 @@ export const createNewTermsAlertType = ( }, }, }, + schemas: { + params: { type: 'zod', schema: NewTermsRuleParams }, + }, actionGroups: [ { id: 'default', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.ts index da5b0631cd98b..284b844773b68 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.ts @@ -52,6 +52,9 @@ export const createQueryAlertType = ( }, }, }, + schemas: { + params: { type: 'zod', schema: UnifiedQueryRuleParams }, + }, actionGroups: [ { id: 'default', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/threshold/create_threshold_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/threshold/create_threshold_alert_type.ts index 459f3da501e2d..11ed8e74e0ca5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/threshold/create_threshold_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/threshold/create_threshold_alert_type.ts @@ -41,6 +41,9 @@ export const createThresholdAlertType = ( }, }, }, + schemas: { + params: { type: 'zod', schema: ThresholdRuleParams }, + }, actionGroups: [ { id: 'default', diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index aaa104533e080..5b8ec7edd36ab 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -46,7 +46,13 @@ import { AppClientFactory } from './client'; import type { ConfigType } from './config'; import { createConfig } from './config'; import { initUiSettings } from './ui_settings'; -import { APP_ID, APP_UI_ID, DEFAULT_ALERTS_INDEX, SERVER_APP_ID } from '../common/constants'; +import { + APP_ID, + APP_UI_ID, + CASE_ATTACHMENT_ENDPOINT_TYPE_ID, + DEFAULT_ALERTS_INDEX, + SERVER_APP_ID, +} from '../common/constants'; import { registerEndpointRoutes } from './endpoint/routes/metadata'; import { registerPolicyRoutes } from './endpoint/routes/policy'; import { registerActionRoutes } from './endpoint/routes/actions'; @@ -230,6 +236,9 @@ export class Plugin implements ISecuritySolutionPlugin { }); this.telemetryUsageCounter = plugins.usageCollection?.createUsageCounter(APP_ID); + plugins.cases.attachmentFramework.registerExternalReference({ + id: CASE_ATTACHMENT_ENDPOINT_TYPE_ID, + }); const { ruleDataService } = plugins.ruleRegistry; let ruleDataClient: IRuleDataClient | null = null; @@ -285,6 +294,7 @@ export class Plugin implements ISecuritySolutionPlugin { scheduleNotificationResponseActionsService: getScheduleNotificationResponseActionsService({ endpointAppContextService: this.endpointAppContextService, osqueryCreateActionService: plugins.osquery.createActionService, + experimentalFeatures: config.experimentalFeatures, }), }; @@ -532,7 +542,7 @@ export class Plugin implements ISecuritySolutionPlugin { artifactClient, exceptionListClient, packagePolicyService: plugins.fleet.packagePolicyService, - logger, + logger: this.pluginContext.logger.get('ManifestManager'), experimentalFeatures: config.experimentalFeatures, packagerTaskPackagePolicyUpdateBatchSize: config.packagerTaskPackagePolicyUpdateBatchSize, esClient: core.elasticsearch.client.asInternalUser, diff --git a/x-pack/plugins/security_solution/server/plugin_contract.ts b/x-pack/plugins/security_solution/server/plugin_contract.ts index 8370e405c6807..a036ef0565fe0 100644 --- a/x-pack/plugins/security_solution/server/plugin_contract.ts +++ b/x-pack/plugins/security_solution/server/plugin_contract.ts @@ -16,7 +16,7 @@ import type { PluginSetupContract as AlertingPluginSetup, PluginStartContract as AlertingPluginStart, } from '@kbn/alerting-plugin/server'; -import type { CasesStart } from '@kbn/cases-plugin/server'; +import type { CasesStart, CasesSetup } from '@kbn/cases-plugin/server'; import type { EncryptedSavedObjectsPluginSetup } from '@kbn/encrypted-saved-objects-plugin/server'; import type { IEventLogClientService, IEventLogService } from '@kbn/event-log-plugin/server'; import type { PluginSetupContract as FeaturesPluginSetup } from '@kbn/features-plugin/server'; @@ -47,6 +47,7 @@ import type { ExperimentalFeatures } from '../common'; export interface SecuritySolutionPluginSetupDependencies { alerting: AlertingPluginSetup; + cases: CasesSetup; cloud: CloudSetup; data: DataPluginSetup; encryptedSavedObjects?: EncryptedSavedObjectsPluginSetup; diff --git a/x-pack/plugins/stack_alerts/server/rule_types/es_query/lib/fetch_esql_query.ts b/x-pack/plugins/stack_alerts/server/rule_types/es_query/lib/fetch_esql_query.ts index b87afae0aec74..87ae2c1123547 100644 --- a/x-pack/plugins/stack_alerts/server/rule_types/es_query/lib/fetch_esql_query.ts +++ b/x-pack/plugins/stack_alerts/server/rule_types/es_query/lib/fetch_esql_query.ts @@ -44,7 +44,7 @@ export async function fetchEsqlQuery({ const response = await esClient.transport.request({ method: 'POST', - path: '/_esql', + path: '/_query', body: query, }); diff --git a/x-pack/plugins/stack_alerts/server/rule_types/es_query/rule_type.ts b/x-pack/plugins/stack_alerts/server/rule_types/es_query/rule_type.ts index 870f30ff2dfcb..b6d0439e3de0d 100644 --- a/x-pack/plugins/stack_alerts/server/rule_types/es_query/rule_type.ts +++ b/x-pack/plugins/stack_alerts/server/rule_types/es_query/rule_type.ts @@ -154,6 +154,12 @@ export function getRuleType( validate: { params: EsQueryRuleParamsSchema, }, + schemas: { + params: { + type: 'config-schema', + schema: EsQueryRuleParamsSchema, + }, + }, actionVariables: { context: [ { name: 'message', description: actionVariableContextMessageLabel }, diff --git a/x-pack/plugins/stack_alerts/server/rule_types/geo_containment/rule_type.ts b/x-pack/plugins/stack_alerts/server/rule_types/geo_containment/rule_type.ts index f7ea3b3601453..2d306eb53da5d 100644 --- a/x-pack/plugins/stack_alerts/server/rule_types/geo_containment/rule_type.ts +++ b/x-pack/plugins/stack_alerts/server/rule_types/geo_containment/rule_type.ts @@ -185,6 +185,12 @@ export function getRuleType(): GeoContainmentRuleType { validate: { params: ParamsSchema, }, + schemas: { + params: { + type: 'config-schema', + schema: ParamsSchema, + }, + }, actionVariables, minimumLicenseRequired: 'gold', isExportable: true, diff --git a/x-pack/plugins/stack_alerts/server/rule_types/index_threshold/rule_type.ts b/x-pack/plugins/stack_alerts/server/rule_types/index_threshold/rule_type.ts index 408f137c731ae..f4d236fe4f995 100644 --- a/x-pack/plugins/stack_alerts/server/rule_types/index_threshold/rule_type.ts +++ b/x-pack/plugins/stack_alerts/server/rule_types/index_threshold/rule_type.ts @@ -179,6 +179,12 @@ export function getRuleType( validate: { params: ParamsSchema, }, + schemas: { + params: { + type: 'config-schema', + schema: ParamsSchema, + }, + }, actionVariables: { context: [ { name: 'message', description: actionVariableContextMessageLabel }, diff --git a/x-pack/plugins/task_manager/server/polling_lifecycle.ts b/x-pack/plugins/task_manager/server/polling_lifecycle.ts index 20a63377b41e0..475895639fc1d 100644 --- a/x-pack/plugins/task_manager/server/polling_lifecycle.ts +++ b/x-pack/plugins/task_manager/server/polling_lifecycle.ts @@ -173,7 +173,7 @@ export class TaskPollingLifecycle implements ITaskEventEmitter, <, =", - "xpack.observability.customThreshold.rule.alertFlyout.error.invalidFilterQuery": "La requête de filtre n'est pas valide.", "xpack.observability.customThreshold.rule.alertFlyout.error.invalidSearchConfiguration": "La vue de données est requise.", "xpack.observability.customThreshold.rule.alertFlyout.error.metrics.aggTypeRequired": "L'agrégation est requise", "xpack.observability.customThreshold.rule.alertFlyout.error.metrics.fieldRequired": "Le champ est obligatoire", @@ -28999,8 +28993,6 @@ "xpack.observability.slo.sloEdit.calendarTimeWindow.monthly": "Mensuel", "xpack.observability.slo.sloEdit.calendarTimeWindow.weekly": "Hebdomadaire", "xpack.observability.slo.sloEdit.cancelButton": "Annuler", - "xpack.observability.slo.sloEdit.createAlert.ruleName": "Règle d'alerte de taux d'avancement du SLO", - "xpack.observability.slo.sloEdit.createAlert.title": "Créer", "xpack.observability.slo.sloEdit.createSloButton": "Créer un SLO", "xpack.observability.slo.sloEdit.customKql.indexSelection.label": "Index", "xpack.observability.slo.sloEdit.dataPreviewChart.errorMessage": "Les paramètres d'indicateur actuels ne sont pas valides", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 6a2a3976b1af9..7c0aa17c81088 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -124,7 +124,7 @@ "charts.legend.toggleLegendButtonAriaLabel": "凡例を切り替える", "charts.legend.toggleLegendButtonTitle": "凡例を切り替える", "charts.noDataLabel": "結果が見つかりませんでした", - "charts.palettes.complimentaryLabel": "無料", + "charts.palettes.complementaryLabel": "無料", "charts.palettes.coolLabel": "Cool", "charts.palettes.customLabel": "カスタム", "charts.palettes.defaultPaletteLabel": "デフォルト", @@ -12777,7 +12777,6 @@ "xpack.enterpriseSearch.content.index.pipelines.ingestFlyout.modalBodyAPIText": "{apiIndex}以下の設定に行われた変更は参照専用です。これらの設定は、インデックスまたはパイプラインまで永続しません。", "xpack.enterpriseSearch.content.indices.configurationConnector.apiKey.description": "まず、Elasticsearch APIキーを生成します。この{apiKeyName}は、コネクターがドキュメントを作成された{indexName}インデックスにインデックスするための読み書き権限を有効にします。キーは安全な場所に保管してください。コネクターを構成するときに必要になります。", "xpack.enterpriseSearch.content.indices.configurationConnector.connectorPackage.connectorConnected": "コネクター{name}は、正常にSearchに接続されました。", - "xpack.enterpriseSearch.content.indices.configurationConnector.connectorPackage.description.secondParagraph": "コネクターリポジトリには複数の{link}が含まれています。当社のフレームワークを使用すると、カスタムデータソース用のコネクターの開発を加速できます。", "xpack.enterpriseSearch.content.indices.configurationConnector.connectorPackage.description.thirdParagraph": "このステップでは、リポジトリを複製またはフォークし、生成されたAPIキーとコネクターIDを、関連付けられた{link}にコピーする必要があります。コネクターIDは、Searchに対するこのコネクターを特定します。サービスタイプは、コネクターが構成されているデータソースのタイプを決定します。", "xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.config.sourceSecurityDocumentationLinkLabel": "{name}認証", "xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.connectorConnected": "コネクター{name}は、正常にSearchに接続されました。", @@ -14086,7 +14085,6 @@ "xpack.enterpriseSearch.content.indices.configurationConnector.apiKey.confirmModal.description": "新しいAPIキーを生成すると、前のキーが無効になります。新しいAPIキーを生成しますか?この操作は元に戻せません。", "xpack.enterpriseSearch.content.indices.configurationConnector.apiKey.confirmModal.title": "Elasticsearch APIキーを生成", "xpack.enterpriseSearch.content.indices.configurationConnector.configuration.successToast.title": "構成が更新されました", - "xpack.enterpriseSearch.content.indices.configurationConnector.connectorPackage.clientExamplesLink": "コネクタークライアントの例", "xpack.enterpriseSearch.content.indices.configurationConnector.connectorPackage.configurationFileLink": "構成ファイル", "xpack.enterpriseSearch.content.indices.configurationConnector.connectorPackage.connectorDeployedText": "構成したら、インフラでコネクターをデプロイします。", "xpack.enterpriseSearch.content.indices.configurationConnector.connectorPackage.waitingForConnector.button.label": "今すぐ再確認", @@ -18379,14 +18377,11 @@ "xpack.idxMgmt.componentTemplatesList.table.deleteActionDescription": "このコンポーネントテンプレートを削除", "xpack.idxMgmt.componentTemplatesList.table.deleteActionLabel": "削除", "xpack.idxMgmt.componentTemplatesList.table.disabledSelectionLabel": "コンポーネントテンプレートは使用中であるため、削除できません", - "xpack.idxMgmt.componentTemplatesList.table.inUseFilterOptionLabel": "使用中", "xpack.idxMgmt.componentTemplatesList.table.isInUseColumnTitle": "使用カウント", - "xpack.idxMgmt.componentTemplatesList.table.isManagedFilterLabel": "管理中", "xpack.idxMgmt.componentTemplatesList.table.managedBadgeLabel": "管理中", "xpack.idxMgmt.componentTemplatesList.table.mappingsColumnTitle": "マッピング", "xpack.idxMgmt.componentTemplatesList.table.nameColumnTitle": "名前", "xpack.idxMgmt.componentTemplatesList.table.notInUseCellDescription": "使用されていません", - "xpack.idxMgmt.componentTemplatesList.table.notInUseFilterOptionLabel": "使用されていません", "xpack.idxMgmt.componentTemplatesList.table.reloadButtonLabel": "再読み込み", "xpack.idxMgmt.componentTemplatesList.table.selectionLabel": "このコンポーネントテンプレートを選択", "xpack.idxMgmt.componentTemplatesList.table.settingsColumnTitle": "設定", @@ -28663,7 +28658,6 @@ "xpack.observability.customThreshold.rule.alertFlyout.dataViewError.noTimestamp": "選択したデータビューにタイムスタンプフィールドがありません。他のデータビューを選択してください。", "xpack.observability.customThreshold.rule.alertFlyout.defineTextQueryPrompt": "クエリフィルターを定義(任意)", "xpack.observability.customThreshold.rule.alertFlyout.error.equation.invalidCharacters": "等式フィールドでは次の文字のみを使用できます:A-Z、+、-、/、*、(、)、?、!、&、:、|、>、<、=", - "xpack.observability.customThreshold.rule.alertFlyout.error.invalidFilterQuery": "フィルタークエリは無効です。", "xpack.observability.customThreshold.rule.alertFlyout.error.invalidSearchConfiguration": "データビューが必要です。", "xpack.observability.customThreshold.rule.alertFlyout.error.metrics.aggTypeRequired": "集約が必要です", "xpack.observability.customThreshold.rule.alertFlyout.error.metrics.fieldRequired": "フィールドが必要です", @@ -29000,8 +28994,6 @@ "xpack.observability.slo.sloEdit.calendarTimeWindow.monthly": "月ごと", "xpack.observability.slo.sloEdit.calendarTimeWindow.weekly": "週ごと", "xpack.observability.slo.sloEdit.cancelButton": "キャンセル", - "xpack.observability.slo.sloEdit.createAlert.ruleName": "SLOバーンレートアラートルール", - "xpack.observability.slo.sloEdit.createAlert.title": "作成", "xpack.observability.slo.sloEdit.createSloButton": "SLOの作成", "xpack.observability.slo.sloEdit.customKql.indexSelection.label": "インデックス", "xpack.observability.slo.sloEdit.dataPreviewChart.errorMessage": "現在のインジケーター設定は無効です", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 8a123f70b214d..55cc9988b9092 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -124,7 +124,7 @@ "charts.legend.toggleLegendButtonAriaLabel": "切换图例", "charts.legend.toggleLegendButtonTitle": "切换图例", "charts.noDataLabel": "找不到结果", - "charts.palettes.complimentaryLabel": "免费", + "charts.palettes.complementaryLabel": "免费", "charts.palettes.coolLabel": "冷", "charts.palettes.customLabel": "定制", "charts.palettes.defaultPaletteLabel": "默认", @@ -12871,7 +12871,6 @@ "xpack.enterpriseSearch.content.index.pipelines.ingestFlyout.modalBodyAPIText": "{apiIndex}对以下设置所做的更改仅供参考。这些设置不会持续用于您的索引或管道。", "xpack.enterpriseSearch.content.indices.configurationConnector.apiKey.description": "首先,生成一个 Elasticsearch API 密钥。此 {apiKeyName} 密钥将为连接器启用读取和写入权限,以便将文档索引到已创建的 {indexName} 索引。请将该密钥保存到安全位置,因为您需要它来配置连接器。", "xpack.enterpriseSearch.content.indices.configurationConnector.connectorPackage.connectorConnected": "您的连接器 {name} 已成功连接到 Search。", - "xpack.enterpriseSearch.content.indices.configurationConnector.connectorPackage.description.secondParagraph": "连接器存储库包含几个 {link}。使用我们的框架可加速为定制数据源开发连接器。", "xpack.enterpriseSearch.content.indices.configurationConnector.connectorPackage.description.thirdParagraph": "在此步骤中,您需要克隆或分叉存储库,然后将生成的 API 密钥和连接器 ID 复制到关联的 {link}。连接器 ID 会将此连接器标识到 Search。此服务类型将决定要将连接器配置用于哪些类型的数据源。", "xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.config.sourceSecurityDocumentationLinkLabel": "{name} 身份验证", "xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.connectorConnected": "您的连接器 {name} 已成功连接到 Search。", @@ -14180,7 +14179,6 @@ "xpack.enterpriseSearch.content.indices.configurationConnector.apiKey.confirmModal.description": "生成新的 API 密钥将使之前的密钥失效。是否确定要生成新的 API 密钥?此操作无法撤消。", "xpack.enterpriseSearch.content.indices.configurationConnector.apiKey.confirmModal.title": "生成 Elasticsearch API 密钥", "xpack.enterpriseSearch.content.indices.configurationConnector.configuration.successToast.title": "已更新配置", - "xpack.enterpriseSearch.content.indices.configurationConnector.connectorPackage.clientExamplesLink": "连接器客户端示例", "xpack.enterpriseSearch.content.indices.configurationConnector.connectorPackage.configurationFileLink": "配置文件", "xpack.enterpriseSearch.content.indices.configurationConnector.connectorPackage.connectorDeployedText": "配置后,请在您的基础设施上部署连接器。", "xpack.enterpriseSearch.content.indices.configurationConnector.connectorPackage.waitingForConnector.button.label": "立即重新检查", @@ -18473,14 +18471,11 @@ "xpack.idxMgmt.componentTemplatesList.table.deleteActionDescription": "删除此组件模板", "xpack.idxMgmt.componentTemplatesList.table.deleteActionLabel": "删除", "xpack.idxMgmt.componentTemplatesList.table.disabledSelectionLabel": "组件模板正在使用中,无法删除", - "xpack.idxMgmt.componentTemplatesList.table.inUseFilterOptionLabel": "在使用中", "xpack.idxMgmt.componentTemplatesList.table.isInUseColumnTitle": "使用计数", - "xpack.idxMgmt.componentTemplatesList.table.isManagedFilterLabel": "托管", "xpack.idxMgmt.componentTemplatesList.table.managedBadgeLabel": "托管", "xpack.idxMgmt.componentTemplatesList.table.mappingsColumnTitle": "映射", "xpack.idxMgmt.componentTemplatesList.table.nameColumnTitle": "名称", "xpack.idxMgmt.componentTemplatesList.table.notInUseCellDescription": "未在使用中", - "xpack.idxMgmt.componentTemplatesList.table.notInUseFilterOptionLabel": "未在使用中", "xpack.idxMgmt.componentTemplatesList.table.reloadButtonLabel": "重新加载", "xpack.idxMgmt.componentTemplatesList.table.selectionLabel": "选择此组件模板", "xpack.idxMgmt.componentTemplatesList.table.settingsColumnTitle": "设置", @@ -28647,7 +28642,6 @@ "xpack.observability.customThreshold.rule.alertFlyout.dataViewError.noTimestamp": "选定数据视图没有时间戳字段,请选择其他数据视图。", "xpack.observability.customThreshold.rule.alertFlyout.defineTextQueryPrompt": "定义查询筛选(可选)", "xpack.observability.customThreshold.rule.alertFlyout.error.equation.invalidCharacters": "方程字段仅支持以下字符:A-Z、+、-、/、*、(、)、?、!、&、:、|、>、<、=", - "xpack.observability.customThreshold.rule.alertFlyout.error.invalidFilterQuery": "筛选查询无效。", "xpack.observability.customThreshold.rule.alertFlyout.error.invalidSearchConfiguration": "需要数据视图。", "xpack.observability.customThreshold.rule.alertFlyout.error.metrics.aggTypeRequired": "“聚合”必填", "xpack.observability.customThreshold.rule.alertFlyout.error.metrics.fieldRequired": "“字段”必填", @@ -28984,8 +28978,6 @@ "xpack.observability.slo.sloEdit.calendarTimeWindow.monthly": "每月", "xpack.observability.slo.sloEdit.calendarTimeWindow.weekly": "每周", "xpack.observability.slo.sloEdit.cancelButton": "取消", - "xpack.observability.slo.sloEdit.createAlert.ruleName": "SLO 消耗速度告警规则", - "xpack.observability.slo.sloEdit.createAlert.title": "创建", "xpack.observability.slo.sloEdit.createSloButton": "创建 SLO", "xpack.observability.slo.sloEdit.customKql.indexSelection.label": "索引", "xpack.observability.slo.sloEdit.dataPreviewChart.errorMessage": "当前指标设置无效", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/alert_mute/use_mute_alert.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/alert_mute/use_mute_alert.test.tsx index 1f40d31fbf79b..d697b59028e7c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/alert_mute/use_mute_alert.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/alert_mute/use_mute_alert.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react-hooks/dom'; import * as api from '../../../../lib/rule_api/mute_alert'; import { waitFor } from '@testing-library/react'; import { useKibana } from '../../../../../common/lib/kibana'; @@ -31,14 +31,12 @@ describe('useMuteAlert', () => { it('calls the api when invoked with the correct parameters', async () => { const muteAlertInstanceSpy = jest.spyOn(api, 'muteAlertInstance'); - const { waitForNextUpdate, result } = renderHook(() => useMuteAlert(), { + const { result } = renderHook(() => useMuteAlert(), { wrapper: appMockRender.AppWrapper, }); result.current.mutate(params); - await waitForNextUpdate(); - await waitFor(() => { expect(muteAlertInstanceSpy).toHaveBeenCalledWith({ id: params.ruleId, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/alert_mute/use_unmute_alert.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/alert_mute/use_unmute_alert.test.tsx index 7e51abd949d60..7d47cd75a386f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/alert_mute/use_unmute_alert.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/alert_mute/use_unmute_alert.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react-hooks/dom'; import * as api from '../../../../lib/rule_api/unmute_alert'; import { waitFor } from '@testing-library/react'; import { useKibana } from '../../../../../common/lib/kibana'; @@ -18,8 +18,7 @@ jest.mock('../../../../../common/lib/kibana'); const params = { ruleId: '', alertInstanceId: '' }; -// FLAKY: https://github.com/elastic/kibana/issues/174900 -describe.skip('useUnmuteAlert', () => { +describe('useUnmuteAlert', () => { const addErrorMock = useKibana().services.notifications.toasts.addError as jest.Mock; let appMockRender: AppMockRenderer; @@ -32,14 +31,12 @@ describe.skip('useUnmuteAlert', () => { it('calls the api when invoked with the correct parameters', async () => { const muteAlertInstanceSpy = jest.spyOn(api, 'unmuteAlertInstance'); - const { waitForNextUpdate, result } = renderHook(() => useUnmuteAlert(), { + const { result } = renderHook(() => useUnmuteAlert(), { wrapper: appMockRender.AppWrapper, }); result.current.mutate(params); - await waitForNextUpdate(); - await waitFor(() => { expect(muteAlertInstanceSpy).toHaveBeenCalledWith({ id: params.ruleId, diff --git a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/group_by_fired.ts b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/group_by_fired.ts index 13ff525daa022..ca73e43114441 100644 --- a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/group_by_fired.ts +++ b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/group_by_fired.ts @@ -262,7 +262,7 @@ export default function ({ getService }: FtrProviderContext) { `https://localhost:5601/app/observability/alerts?_a=(kuery:%27kibana.alert.uuid:%20%22${alertId}%22%27%2CrangeFrom:%27${rangeFrom}%27%2CrangeTo:now%2Cstatus:all)` ); expect(resp.hits.hits[0]._source?.reason).eql( - `Average system.cpu.total.norm.pct is 80%, above the threshold of 20%. (duration: 1 min, data view: ${DATA_VIEW}, group: host-0,container-0)` + `Average system.cpu.total.norm.pct is 80%, above or equal the threshold of 20%. (duration: 1 min, data view: ${DATA_VIEW}, group: host-0,container-0)` ); expect(resp.hits.hits[0]._source?.value).eql('80%'); expect(resp.hits.hits[0]._source?.host).eql( diff --git a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/p99_pct_fired.ts b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/p99_pct_fired.ts new file mode 100644 index 0000000000000..942d025dd11f3 --- /dev/null +++ b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/p99_pct_fired.ts @@ -0,0 +1,259 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { omit } from 'lodash'; +import { cleanup, generate, Dataset, PartialConfig } from '@kbn/data-forge'; +import { + Aggregators, + Comparator, +} from '@kbn/observability-plugin/common/custom_threshold_rule/types'; +import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/custom_threshold/constants'; +import expect from '@kbn/expect'; +import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/rule-data-utils'; +import { parseSearchParams } from '@kbn/share-plugin/common/url_service'; +import { createIndexConnector, createRule } from '../helpers/alerting_api_helper'; +import { + waitForAlertInIndex, + waitForDocumentInIndex, + waitForRuleStatus, +} from '../helpers/alerting_wait_for_helpers'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { ActionDocument, LogsExplorerLocatorParsedParams } from './typings'; +import { ISO_DATE_REGEX } from './constants'; + +// eslint-disable-next-line import/no-default-export +export default function ({ getService }: FtrProviderContext) { + const esClient = getService('es'); + const supertest = getService('supertest'); + const esDeleteAllIndices = getService('esDeleteAllIndices'); + const logger = getService('log'); + + describe('Custom Threshold rule - P99 - PCT - FIRED', () => { + const CUSTOM_THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; + const ALERT_ACTION_INDEX = 'alert-action-threshold'; + const DATE_VIEW_TITLE = 'kbn-data-forge-fake_hosts.fake_hosts-*'; + const DATE_VIEW_NAME = 'ad-hoc-data-view-name'; + const DATA_VIEW_ID = 'data-view-id'; + const MOCKED_AD_HOC_DATA_VIEW = { + id: DATA_VIEW_ID, + title: DATE_VIEW_TITLE, + timeFieldName: '@timestamp', + sourceFilters: [], + fieldFormats: {}, + runtimeFieldMap: {}, + allowNoIndex: false, + name: DATE_VIEW_NAME, + allowHidden: false, + }; + let dataForgeConfig: PartialConfig; + let dataForgeIndices: string[]; + let actionId: string; + let ruleId: string; + let alertId: string; + let startedAt: string; + + before(async () => { + dataForgeConfig = { + schedule: [ + { + template: 'good', + start: 'now-15m', + end: 'now', + metrics: [{ name: 'system.cpu.user.pct', method: 'linear', start: 2.5, end: 2.5 }], + }, + ], + indexing: { + dataset: 'fake_hosts' as Dataset, + eventsPerCycle: 1, + interval: 10000, + alignEventsToInterval: true, + }, + }; + dataForgeIndices = await generate({ client: esClient, config: dataForgeConfig, logger }); + logger.info(JSON.stringify(dataForgeIndices.join(','))); + await waitForDocumentInIndex({ + esClient, + indexName: DATE_VIEW_TITLE, + docCountTarget: 270, + }); + }); + + after(async () => { + await supertest.delete(`/api/alerting/rule/${ruleId}`).set('kbn-xsrf', 'foo'); + await supertest.delete(`/api/actions/connector/${actionId}`).set('kbn-xsrf', 'foo'); + await esClient.deleteByQuery({ + index: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, + query: { term: { 'kibana.alert.rule.uuid': ruleId } }, + }); + await esClient.deleteByQuery({ + index: '.kibana-event-log-*', + query: { term: { 'kibana.alert.rule.consumer': 'logs' } }, + }); + await esDeleteAllIndices([ALERT_ACTION_INDEX, ...dataForgeIndices]); + await cleanup({ client: esClient, config: dataForgeConfig, logger }); + }); + + describe('Rule creation', () => { + it('creates rule successfully', async () => { + actionId = await createIndexConnector({ + supertest, + name: 'Index Connector: Threshold API test', + indexName: ALERT_ACTION_INDEX, + }); + + const createdRule = await createRule({ + supertest, + tags: ['observability'], + consumer: 'logs', + name: 'Threshold rule', + ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, + params: { + criteria: [ + { + comparator: Comparator.GT, + threshold: [0.5], + timeSize: 5, + timeUnit: 'm', + metrics: [{ name: 'A', field: 'system.cpu.user.pct', aggType: Aggregators.P99 }], + }, + ], + alertOnNoData: true, + alertOnGroupDisappear: true, + searchConfiguration: { + query: { + query: '', + language: 'kuery', + }, + index: MOCKED_AD_HOC_DATA_VIEW, + }, + }, + actions: [ + { + group: FIRED_ACTIONS_ID, + id: actionId, + params: { + documents: [ + { + ruleType: '{{rule.type}}', + alertDetailsUrl: '{{context.alertDetailsUrl}}', + reason: '{{context.reason}}', + value: '{{context.value}}', + host: '{{context.host}}', + viewInAppUrl: '{{context.viewInAppUrl}}', + }, + ], + }, + frequency: { + notify_when: 'onActionGroupChange', + throttle: null, + summary: false, + }, + }, + ], + }); + ruleId = createdRule.id; + expect(ruleId).not.to.be(undefined); + }); + + it('should be active', async () => { + const executionStatus = await waitForRuleStatus({ + id: ruleId, + expectedStatus: 'active', + supertest, + }); + expect(executionStatus.status).to.be('active'); + }); + + it('should set correct information in the alert document', async () => { + const resp = await waitForAlertInIndex({ + esClient, + indexName: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, + ruleId, + }); + alertId = (resp.hits.hits[0]._source as any)['kibana.alert.uuid']; + startedAt = (resp.hits.hits[0]._source as any)['kibana.alert.start']; + + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.rule.category', + 'Custom threshold (Beta)' + ); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'logs'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.rule.rule_type_id', + 'observability.rules.custom_threshold' + ); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.uuid', ruleId); + expect(resp.hits.hits[0]._source).property('kibana.space_ids').contain('default'); + expect(resp.hits.hits[0]._source) + .property('kibana.alert.rule.tags') + .contain('observability'); + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.action_group', + 'custom_threshold.fired' + ); + expect(resp.hits.hits[0]._source).property('tags').contain('observability'); + expect(resp.hits.hits[0]._source).property('kibana.alert.instance.id', '*'); + expect(resp.hits.hits[0]._source).property('kibana.alert.workflow_status', 'open'); + expect(resp.hits.hits[0]._source).property('event.kind', 'signal'); + expect(resp.hits.hits[0]._source).property('event.action', 'open'); + + expect(resp.hits.hits[0]._source) + .property('kibana.alert.rule.parameters') + .eql({ + criteria: [ + { + comparator: '>', + threshold: [0.5], + timeSize: 5, + timeUnit: 'm', + metrics: [{ name: 'A', field: 'system.cpu.user.pct', aggType: 'p99' }], + }, + ], + alertOnNoData: true, + alertOnGroupDisappear: true, + searchConfiguration: { + index: MOCKED_AD_HOC_DATA_VIEW, + query: { query: '', language: 'kuery' }, + }, + }); + }); + + it('should set correct action variables', async () => { + const rangeFrom = moment(startedAt).subtract('5', 'minute').toISOString(); + const resp = await waitForDocumentInIndex({ + esClient, + indexName: ALERT_ACTION_INDEX, + }); + + expect(resp.hits.hits[0]._source?.ruleType).eql('observability.rules.custom_threshold'); + expect(resp.hits.hits[0]._source?.alertDetailsUrl).eql( + `https://localhost:5601/app/observability/alerts?_a=(kuery:%27kibana.alert.uuid:%20%22${alertId}%22%27%2CrangeFrom:%27${rangeFrom}%27%2CrangeTo:now%2Cstatus:all)` + ); + expect(resp.hits.hits[0]._source?.reason).eql( + `99th percentile of system.cpu.user.pct is 250%, above the threshold of 50%. (duration: 5 mins, data view: ${DATE_VIEW_NAME})` + ); + expect(resp.hits.hits[0]._source?.value).eql('250%'); + + const parsedViewInAppUrl = parseSearchParams( + new URL(resp.hits.hits[0]._source?.viewInAppUrl || '').search + ); + + expect(resp.hits.hits[0]._source?.viewInAppUrl).contain('LOGS_EXPLORER_LOCATOR'); + expect(omit(parsedViewInAppUrl.params, 'timeRange.from')).eql({ + dataset: DATE_VIEW_TITLE, + timeRange: { to: 'now' }, + query: { query: '', language: 'kuery' }, + }); + expect(parsedViewInAppUrl.params.timeRange.from).match(ISO_DATE_REGEX); + }); + }); + }); +} diff --git a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/rate_bytes_fired.ts b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/rate_bytes_fired.ts new file mode 100644 index 0000000000000..b76dd0c2581fc --- /dev/null +++ b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/rate_bytes_fired.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import moment from 'moment'; +import { cleanup, generate, Dataset, PartialConfig } from '@kbn/data-forge'; +import { + Aggregators, + Comparator, +} from '@kbn/observability-plugin/common/custom_threshold_rule/types'; +import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/custom_threshold/constants'; +import expect from '@kbn/expect'; +import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/rule-data-utils'; +import { createIndexConnector, createRule } from '../helpers/alerting_api_helper'; +import { createDataView, deleteDataView } from '../helpers/data_view'; +import { + waitForAlertInIndex, + waitForDocumentInIndex, + waitForRuleStatus, +} from '../helpers/alerting_wait_for_helpers'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { ActionDocument } from './typings'; + +// eslint-disable-next-line import/no-default-export +export default function ({ getService }: FtrProviderContext) { + const esClient = getService('es'); + const supertest = getService('supertest'); + const esDeleteAllIndices = getService('esDeleteAllIndices'); + const logger = getService('log'); + + describe('Custom Threshold rule RATE - GROUP_BY - BYTES - FIRED', () => { + const CUSTOM_THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; + const ALERT_ACTION_INDEX = 'alert-action-threshold'; + const DATE_VIEW = 'kbn-data-forge-fake_hosts.fake_hosts-*'; + const DATA_VIEW_ID = 'data-view-id'; + let dataForgeConfig: PartialConfig; + let dataForgeIndices: string[]; + let actionId: string; + let ruleId: string; + let alertId: string; + let startedAt: string; + + before(async () => { + dataForgeConfig = { + schedule: [ + { + template: 'good', + start: 'now-15m', + end: 'now', + metrics: [{ name: 'system.network.in.bytes', method: 'exp', start: 10, end: 100 }], + }, + ], + indexing: { + dataset: 'fake_hosts' as Dataset, + eventsPerCycle: 1, + interval: 10000, + alignEventsToInterval: true, + }, + }; + dataForgeIndices = await generate({ client: esClient, config: dataForgeConfig, logger }); + await waitForDocumentInIndex({ + esClient, + indexName: dataForgeIndices.join(','), + docCountTarget: 270, + }); + await createDataView({ + supertest, + name: DATE_VIEW, + id: DATA_VIEW_ID, + title: DATE_VIEW, + }); + }); + + after(async () => { + await supertest.delete(`/api/alerting/rule/${ruleId}`).set('kbn-xsrf', 'foo'); + await supertest.delete(`/api/actions/connector/${actionId}`).set('kbn-xsrf', 'foo'); + await esClient.deleteByQuery({ + index: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, + query: { term: { 'kibana.alert.rule.uuid': ruleId } }, + }); + await esClient.deleteByQuery({ + index: '.kibana-event-log-*', + query: { term: { 'kibana.alert.rule.consumer': 'logs' } }, + }); + await deleteDataView({ + supertest, + id: DATA_VIEW_ID, + }); + await esDeleteAllIndices([ALERT_ACTION_INDEX, ...dataForgeIndices]); + await cleanup({ client: esClient, config: dataForgeConfig, logger }); + }); + + describe('Rule creation', () => { + it('creates rule successfully', async () => { + actionId = await createIndexConnector({ + supertest, + name: 'Index Connector: Threshold API test', + indexName: ALERT_ACTION_INDEX, + }); + + const createdRule = await createRule({ + supertest, + tags: ['observability'], + consumer: 'logs', + name: 'Threshold rule', + ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, + params: { + criteria: [ + { + comparator: Comparator.GT_OR_EQ, + threshold: [0.2], + timeSize: 1, + timeUnit: 'm', + metrics: [ + { name: 'A', field: 'system.network.in.bytes', aggType: Aggregators.RATE }, + ], + }, + ], + alertOnNoData: true, + alertOnGroupDisappear: true, + searchConfiguration: { + query: { + query: '', + language: 'kuery', + }, + index: DATA_VIEW_ID, + }, + groupBy: ['host.name', 'container.id'], + }, + actions: [ + { + group: FIRED_ACTIONS_ID, + id: actionId, + params: { + documents: [ + { + ruleType: '{{rule.type}}', + alertDetailsUrl: '{{context.alertDetailsUrl}}', + reason: '{{context.reason}}', + value: '{{context.value}}', + host: '{{context.host}}', + group: '{{context.group}}', + }, + ], + }, + frequency: { + notify_when: 'onActionGroupChange', + throttle: null, + summary: false, + }, + }, + ], + }); + ruleId = createdRule.id; + expect(ruleId).not.to.be(undefined); + }); + + it('should be active', async () => { + const executionStatus = await waitForRuleStatus({ + id: ruleId, + expectedStatus: 'active', + supertest, + }); + expect(executionStatus.status).to.be('active'); + }); + + it('should set correct information in the alert document', async () => { + const resp = await waitForAlertInIndex({ + esClient, + indexName: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, + ruleId, + }); + alertId = (resp.hits.hits[0]._source as any)['kibana.alert.uuid']; + startedAt = (resp.hits.hits[0]._source as any)['kibana.alert.start']; + + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.rule.category', + 'Custom threshold (Beta)' + ); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'logs'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.rule.rule_type_id', + 'observability.rules.custom_threshold' + ); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.uuid', ruleId); + expect(resp.hits.hits[0]._source).property('kibana.space_ids').contain('default'); + expect(resp.hits.hits[0]._source) + .property('kibana.alert.rule.tags') + .contain('observability'); + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.action_group', + 'custom_threshold.fired' + ); + expect(resp.hits.hits[0]._source).property('tags').contain('observability'); + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.instance.id', + 'host-0,container-0' + ); + expect(resp.hits.hits[0]._source).property('kibana.alert.workflow_status', 'open'); + expect(resp.hits.hits[0]._source).property('event.kind', 'signal'); + expect(resp.hits.hits[0]._source).property('event.action', 'open'); + + expect(resp.hits.hits[0]._source).property('host.name', 'host-0'); + expect(resp.hits.hits[0]._source) + .property('host.mac') + .eql(['00-00-5E-00-53-23', '00-00-5E-00-53-24']); + expect(resp.hits.hits[0]._source).property('container.id', 'container-0'); + expect(resp.hits.hits[0]._source).property('container.name', 'container-name'); + expect(resp.hits.hits[0]._source).not.property('container.cpu'); + + expect(resp.hits.hits[0]._source) + .property('kibana.alert.group') + .eql([ + { + field: 'host.name', + value: 'host-0', + }, + { + field: 'container.id', + value: 'container-0', + }, + ]); + + expect(resp.hits.hits[0]._source) + .property('kibana.alert.rule.parameters') + .eql({ + criteria: [ + { + comparator: '>=', + threshold: [0.2], + timeSize: 1, + timeUnit: 'm', + metrics: [{ name: 'A', field: 'system.network.in.bytes', aggType: 'rate' }], + }, + ], + alertOnNoData: true, + alertOnGroupDisappear: true, + searchConfiguration: { index: 'data-view-id', query: { query: '', language: 'kuery' } }, + groupBy: ['host.name', 'container.id'], + }); + }); + + it('should set correct action variables', async () => { + const rangeFrom = moment(startedAt).subtract('5', 'minute').toISOString(); + const resp = await waitForDocumentInIndex({ + esClient, + indexName: ALERT_ACTION_INDEX, + }); + + expect(resp.hits.hits[0]._source?.ruleType).eql('observability.rules.custom_threshold'); + expect(resp.hits.hits[0]._source?.alertDetailsUrl).eql( + `https://localhost:5601/app/observability/alerts?_a=(kuery:%27kibana.alert.uuid:%20%22${alertId}%22%27%2CrangeFrom:%27${rangeFrom}%27%2CrangeTo:now%2Cstatus:all)` + ); + expect(resp.hits.hits[0]._source?.reason).eql( + `Rate of system.network.in.bytes is 0.3 B/s, above or equal the threshold of 0.2 B/s. (duration: 1 min, data view: kbn-data-forge-fake_hosts.fake_hosts-*, group: host-0,container-0)` + ); + expect(resp.hits.hits[0]._source?.value).eql('0.3 B/s'); + expect(resp.hits.hits[0]._source?.host).eql( + '{"name":"host-0","mac":["00-00-5E-00-53-23","00-00-5E-00-53-24"]}' + ); + expect(resp.hits.hits[0]._source?.group).eql( + '{"field":"host.name","value":"host-0"},{"field":"container.id","value":"container-0"}' + ); + }); + }); + }); +} diff --git a/x-pack/test/alerting_api_integration/observability/index.ts b/x-pack/test/alerting_api_integration/observability/index.ts index 884c17d2abfd1..812123dd96b13 100644 --- a/x-pack/test/alerting_api_integration/observability/index.ts +++ b/x-pack/test/alerting_api_integration/observability/index.ts @@ -11,6 +11,8 @@ export default function ({ loadTestFile }: any) { describe('Rules Endpoints', () => { loadTestFile(require.resolve('./metric_threshold_rule')); loadTestFile(require.resolve('./custom_threshold_rule/avg_pct_fired')); + loadTestFile(require.resolve('./custom_threshold_rule/p99_pct_fired')); + loadTestFile(require.resolve('./custom_threshold_rule/rate_bytes_fired')); loadTestFile(require.resolve('./custom_threshold_rule/avg_pct_no_data')); loadTestFile(require.resolve('./custom_threshold_rule/avg_us_fired')); loadTestFile(require.resolve('./custom_threshold_rule/custom_eq_avg_bytes_fired')); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/bulk_untrack.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/bulk_untrack.ts index b2710626032fe..8a9225694047c 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/bulk_untrack.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/bulk_untrack.ts @@ -21,6 +21,15 @@ export default function bulkUntrackTests({ getService }: FtrProviderContext) { const retry = getService('retry'); const es = getService('es'); + const runSoon = async (id: string) => { + return retry.try(async () => { + await supertest + .post(`${getUrlPrefix('space1')}/internal/alerting/rule/${id}/_run_soon`) + .set('kbn-xsrf', 'foo') + .expect(204); + }); + }; + describe('bulk untrack', () => { const objectRemover = new ObjectRemover(supertest); @@ -128,5 +137,90 @@ export default function bulkUntrackTests({ getService }: FtrProviderContext) { }); }); } + + it('should create new alerts if run rules again after alerts are untracked', async () => { + const { body: createdRule } = await supertest + .post(`${getUrlPrefix('space1')}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send( + getTestRuleData({ + rule_type_id: 'test.always-firing-alert-as-data', + schedule: { interval: '24h' }, + throttle: undefined, + notify_when: undefined, + params: { + index: ES_TEST_INDEX_NAME, + reference: 'test', + }, + }) + ) + .expect(200); + + objectRemover.add('space1', createdRule.id, 'rule', 'alerting'); + + await retry.try(async () => { + return await getEventLog({ + getService, + spaceId: 'space1', + type: 'alert', + id: createdRule.id, + provider: 'alerting', + actions: new Map([['active-instance', { equal: 2 }]]), + }); + }); + + const { + hits: { hits: activeAlerts }, + } = await es.search({ + index: alertAsDataIndex, + body: { query: { match_all: {} } }, + }); + + const ids = activeAlerts.map((activeAlert: any) => activeAlert._source[ALERT_UUID]); + + await supertest + .post(`${getUrlPrefix('space1')}/internal/alerting/alerts/_bulk_untrack`) + .set('kbn-xsrf', 'foo') + .send({ + indices: [alertAsDataIndex], + alert_uuids: ids, + }); + + await runSoon(createdRule.id); + + await retry.try(async () => { + return await getEventLog({ + getService, + spaceId: 'space1', + type: 'alert', + id: createdRule.id, + provider: 'alerting', + actions: new Map([['active-instance', { equal: 4 }]]), + }); + }); + + await retry.try(async () => { + const { + hits: { hits: alerts }, + } = await es.search({ + index: alertAsDataIndex, + body: { query: { match_all: {} } }, + }); + + const activeAlertsRemaining = []; + const untrackedAlertsRemaining = []; + + alerts.forEach((alert: any) => { + if (alert._source[ALERT_STATUS] === 'active') { + activeAlertsRemaining.push(alert); + } else { + untrackedAlertsRemaining.push(alert); + } + }); + + expect(activeAlertsRemaining.length).eql(2); + expect(untrackedAlertsRemaining.length).eql(2); + }); + }); }); } diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/event_log.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/event_log.ts index 5228b1c76d3d9..da3752e098de2 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/event_log.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/event_log.ts @@ -7,6 +7,7 @@ import moment from 'moment'; import expect from '@kbn/expect'; +import { get } from 'lodash'; import { IValidatedEvent, nanosToMillis } from '@kbn/event-log-plugin/server'; import { RuleNotifyWhen } from '@kbn/alerting-plugin/common'; import { ES_TEST_INDEX_NAME, ESTestIndexTool } from '@kbn/alerting-api-integration-helpers'; @@ -1849,7 +1850,12 @@ export default function eventLogTests({ getService }: FtrProviderContext) { expect(hasActions).eql(false); }); - it('should generate expected events with a notificationDelay', async () => { + it('should generate expected events with a alertDelay', async () => { + const ACTIVE_PATH = 'kibana.alert.rule.execution.metrics.alert_counts.active'; + const NEW_PATH = 'kibana.alert.rule.execution.metrics.alert_counts.new'; + const RECOVERED_PATH = 'kibana.alert.rule.execution.metrics.alert_counts.recovered'; + const ACTION_PATH = 'kibana.alert.rule.execution.metrics.number_of_triggered_actions'; + const { body: createdAction } = await supertest .post(`${getUrlPrefix(space.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') @@ -1863,7 +1869,7 @@ export default function eventLogTests({ getService }: FtrProviderContext) { // pattern of when the alert should fire const pattern = { - instance: [true, true, true, false, true], + instance: [true, true, true, true, false, true], }; const response = await supertest @@ -1874,6 +1880,7 @@ export default function eventLogTests({ getService }: FtrProviderContext) { rule_type_id: 'test.patternFiring', schedule: { interval: '1s' }, throttle: null, + notify_when: null, params: { pattern, }, @@ -1882,9 +1889,14 @@ export default function eventLogTests({ getService }: FtrProviderContext) { id: createdAction.id, group: 'default', params: {}, + frequency: { + summary: false, + throttle: null, + notify_when: RuleNotifyWhen.CHANGE, + }, }, ], - notification_delay: { + alert_delay: { active: 3, }, }) @@ -1904,101 +1916,48 @@ export default function eventLogTests({ getService }: FtrProviderContext) { provider: 'alerting', actions: new Map([ // make sure the counts of the # of events per type are as expected - ['execute-start', { gte: 5 }], - ['execute', { gte: 5 }], - ['new-instance', { equal: 2 }], - ['active-instance', { gte: 1 }], + ['execute-start', { equal: 6 }], + ['execute', { equal: 6 }], + ['new-instance', { equal: 1 }], + ['active-instance', { equal: 2 }], ['recovered-instance', { equal: 1 }], ]), }); }); - const actualTriggeredActions = events - .filter((event) => event?.event?.action === 'execute') - .reduce( - (acc, event) => - acc + - (event?.kibana?.alert?.rule?.execution?.metrics - ?.number_of_triggered_actions as number), - 0 - ); - expect(actualTriggeredActions).to.eql(1); - }); - - it('should generate expected events with a notificationDelay with AAD', async () => { - const { body: createdAction } = await supertest - .post(`${getUrlPrefix(space.id)}/api/actions/connector`) - .set('kbn-xsrf', 'foo') - .send({ - name: 'MY action', - connector_type_id: 'test.noop', - config: {}, - secrets: {}, - }) - .expect(200); - - // pattern of when the alert should fire - const pattern = { - instance: [true, true, true, false, true], - }; - - const response = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) - .set('kbn-xsrf', 'foo') - .send( - getTestRuleData({ - rule_type_id: 'test.patternFiringAad', - schedule: { interval: '1s' }, - throttle: null, - params: { - pattern, - }, - actions: [ - { - id: createdAction.id, - group: 'default', - params: {}, - }, - ], - notification_delay: { - active: 3, - }, - }) - ); - - expect(response.status).to.eql(200); - const alertId = response.body.id; - objectRemover.add(space.id, alertId, 'rule', 'alerting'); + const executeEvents = events.filter((event) => event?.event?.action === 'execute'); - // get the events we're expecting - const events = await retry.try(async () => { - return await getEventLog({ - getService, - spaceId: space.id, - type: 'alert', - id: alertId, - provider: 'alerting', - actions: new Map([ - // make sure the counts of the # of events per type are as expected - ['execute-start', { gte: 5 }], - ['execute', { gte: 5 }], - ['new-instance', { equal: 2 }], - ['active-instance', { gte: 1 }], - ['recovered-instance', { equal: 1 }], - ]), - }); + // first two executions do not create the active alert + executeEvents.slice(0, 1).forEach((event) => { + expect(get(event, ACTIVE_PATH)).to.be(0); + expect(get(event, NEW_PATH)).to.be(0); + expect(get(event, RECOVERED_PATH)).to.be(0); + expect(get(event, ACTION_PATH)).to.be(0); }); - const actualTriggeredActions = events - .filter((event) => event?.event?.action === 'execute') - .reduce( - (acc, event) => - acc + - (event?.kibana?.alert?.rule?.execution?.metrics - ?.number_of_triggered_actions as number), - 0 - ); - expect(actualTriggeredActions).to.eql(1); + // third executions creates the delayed active alert and triggers actions + expect(get(executeEvents[2], ACTIVE_PATH)).to.be(1); + expect(get(executeEvents[2], NEW_PATH)).to.be(1); + expect(get(executeEvents[2], RECOVERED_PATH)).to.be(0); + expect(get(executeEvents[2], ACTION_PATH)).to.be(1); + + // fourth execution + expect(get(executeEvents[3], ACTIVE_PATH)).to.be(1); + expect(get(executeEvents[3], NEW_PATH)).to.be(0); + expect(get(executeEvents[3], RECOVERED_PATH)).to.be(0); + expect(get(executeEvents[3], ACTION_PATH)).to.be(0); + + // fifth recovered execution + expect(get(executeEvents[4], ACTIVE_PATH)).to.be(0); + expect(get(executeEvents[4], NEW_PATH)).to.be(0); + expect(get(executeEvents[4], RECOVERED_PATH)).to.be(1); + expect(get(executeEvents[4], ACTION_PATH)).to.be(0); + + // sixth execution does not create the active alert + expect(get(executeEvents[5], ACTIVE_PATH)).to.be(0); + expect(get(executeEvents[5], NEW_PATH)).to.be(0); + expect(get(executeEvents[5], RECOVERED_PATH)).to.be(0); + expect(get(executeEvents[5], ACTION_PATH)).to.be(0); }); }); } diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/es_query/esql_only.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/es_query/esql_only.ts index 9e4aec78ece28..f193af1b81703 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/es_query/esql_only.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/es_query/esql_only.ts @@ -45,8 +45,7 @@ export default function ruleTests({ getService }: FtrProviderContext) { { label: 'host.name', searchPath: 'host.name' }, ]; - // FLAKY: https://github.com/elastic/kibana/issues/176116 - describe.skip('rule', async () => { + describe('rule', async () => { let endDate: string; let connectorId: string; const objectRemover = new ObjectRemover(supertest); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/notification_delay.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alert_delay.ts similarity index 82% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/notification_delay.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alert_delay.ts index 2b632686d5793..7062c1c65fd9c 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/notification_delay.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alert_delay.ts @@ -12,7 +12,7 @@ import { getUrlPrefix, getTestRuleData, ObjectRemover } from '../../../../common import { Spaces } from '../../../scenarios'; // eslint-disable-next-line import/no-default-export -export default function createNotificationDelayTests({ getService }: FtrProviderContext) { +export default function createAlertDelayTests({ getService }: FtrProviderContext) { const es = getService('es'); const supertestWithoutAuth = getService('supertestWithoutAuth'); const retry = getService('retry'); @@ -22,7 +22,7 @@ export default function createNotificationDelayTests({ getService }: FtrProvider const ACTIVE_PATH = 'alertInstances.instance.meta.activeCount'; const RECOVERED_PATH = 'alertRecoveredInstances.instance.meta.activeCount'; - describe('Notification Delay', () => { + describe('Alert Delay', () => { let actionId: string; const objectRemover = new ObjectRemover(supertestWithoutAuth); @@ -37,20 +37,7 @@ export default function createNotificationDelayTests({ getService }: FtrProvider afterEach(() => objectRemover.removeAll()); - it('should clear the activeCount if the notificationDelay is not configured for the rule', async () => { - const start = new Date().toISOString(); - const pattern = { - instance: [true], - }; - - const ruleId = await createRule(actionId, pattern); - objectRemover.add(space.id, ruleId, 'rule', 'alerting'); - - const state = await getAlertState(start, ruleId, 0); - expect(get(state, ACTIVE_PATH)).to.eql(0); - }); - - it('should update the activeCount when alert is active and clear when recovered if the notificationDelay is configured for the rule', async () => { + it('should update the activeCount when alert is active and clear when recovered', async () => { let start = new Date().toISOString(); const pattern = { instance: [true, true, true, false, true], @@ -79,7 +66,7 @@ export default function createNotificationDelayTests({ getService }: FtrProvider expect(get(state, ACTIVE_PATH)).to.eql(1); }); - it('should reset the activeCount when count of consecutive active alerts exceeds the notificationDelay count', async () => { + it('should continue incrementing the activeCount when count of consecutive active alerts exceeds the alertDelay count', async () => { let start = new Date().toISOString(); const pattern = { instance: [true, true, true, true, true], @@ -96,16 +83,16 @@ export default function createNotificationDelayTests({ getService }: FtrProvider expect(get(state, ACTIVE_PATH)).to.eql(2); start = new Date().toISOString(); - state = await getAlertState(start, ruleId, 0, true); - expect(get(state, ACTIVE_PATH)).to.eql(0); + state = await getAlertState(start, ruleId, 3, true); + expect(get(state, ACTIVE_PATH)).to.eql(3); start = new Date().toISOString(); - state = await getAlertState(start, ruleId, 1, true); - expect(get(state, ACTIVE_PATH)).to.eql(1); + state = await getAlertState(start, ruleId, 4, true); + expect(get(state, ACTIVE_PATH)).to.eql(4); start = new Date().toISOString(); - state = await getAlertState(start, ruleId, 2, true); - expect(get(state, ACTIVE_PATH)).to.eql(2); + state = await getAlertState(start, ruleId, 5, true); + expect(get(state, ACTIVE_PATH)).to.eql(5); }); }); @@ -187,7 +174,7 @@ export default function createNotificationDelayTests({ getService }: FtrProvider params: {}, }, ], - ...(activeCount ? { notification_delay: { active: activeCount } } : {}), + ...(activeCount ? { alert_delay: { active: activeCount } } : {}), }) ) .expect(200); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data/alerts_as_data_alert_delay.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data/alerts_as_data_alert_delay.ts new file mode 100644 index 0000000000000..f7e2876e9775b --- /dev/null +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data/alerts_as_data_alert_delay.ts @@ -0,0 +1,665 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { get } from 'lodash'; +import { SearchHit } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { IValidatedEvent } from '@kbn/event-log-plugin/server'; +import type { Alert } from '@kbn/alerts-as-data-utils'; +import { + ALERT_ACTION_GROUP, + ALERT_DURATION, + ALERT_END, + ALERT_RULE_CATEGORY, + ALERT_RULE_CONSUMER, + ALERT_RULE_EXECUTION_UUID, + ALERT_RULE_NAME, + ALERT_RULE_PARAMETERS, + ALERT_RULE_PRODUCER, + ALERT_RULE_TAGS, + ALERT_RULE_TYPE_ID, + ALERT_RULE_UUID, + ALERT_START, + ALERT_STATUS, + ALERT_TIME_RANGE, + ALERT_UUID, + ALERT_WORKFLOW_STATUS, + EVENT_ACTION, + EVENT_KIND, + SPACE_IDS, +} from '@kbn/rule-data-utils'; +import { RuleNotifyWhen } from '@kbn/alerting-plugin/common'; +import { ES_TEST_INDEX_NAME, ESTestIndexTool } from '@kbn/alerting-api-integration-helpers'; +import { FtrProviderContext } from '../../../../../common/ftr_provider_context'; +import { Spaces } from '../../../../scenarios'; +import { + getEventLog, + getTestRuleData, + getUrlPrefix, + ObjectRemover, + TaskManagerDoc, +} from '../../../../../common/lib'; + +// eslint-disable-next-line import/no-default-export +export default function createAlertsAsDataAlertDelayInstallResourcesTest({ + getService, +}: FtrProviderContext) { + const ACTIVE_PATH = 'kibana.alert.rule.execution.metrics.alert_counts.active'; + const NEW_PATH = 'kibana.alert.rule.execution.metrics.alert_counts.new'; + const RECOVERED_PATH = 'kibana.alert.rule.execution.metrics.alert_counts.recovered'; + const ACTION_PATH = 'kibana.alert.rule.execution.metrics.number_of_triggered_actions'; + const UUID_PATH = 'kibana.alert.rule.execution.uuid'; + + const es = getService('es'); + const retry = getService('retry'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + const objectRemover = new ObjectRemover(supertestWithoutAuth); + const esTestIndexTool = new ESTestIndexTool(es, retry); + + type PatternFiringAlert = Alert & { patternIndex: number; instancePattern: boolean[] }; + // type AlwaysFiringAlert = Alert & { patternIndex: number; instancePattern: boolean[] }; + + const alertsAsDataIndex = '.alerts-test.patternfiring.alerts-default'; + const alwaysFiringAlertsAsDataIndex = + '.internal.alerts-observability.test.alerts.alerts-default-000001'; + const timestampPattern = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/; + + describe('alerts as data', () => { + before(async () => { + await esTestIndexTool.destroy(); + await esTestIndexTool.setup(); + await es.deleteByQuery({ + index: [alertsAsDataIndex, alwaysFiringAlertsAsDataIndex], + query: { match_all: {} }, + conflicts: 'proceed', + }); + }); + afterEach(() => objectRemover.removeAll()); + after(async () => { + await objectRemover.removeAll(); + await esTestIndexTool.destroy(); + await es.deleteByQuery({ + index: [alertsAsDataIndex, alwaysFiringAlertsAsDataIndex], + query: { match_all: {} }, + conflicts: 'proceed', + }); + }); + + it('should generate expected events with a alertDelay with AAD', async () => { + const { body: createdAction } = await supertestWithoutAuth + .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'MY action', + connector_type_id: 'test.noop', + config: {}, + secrets: {}, + }) + .expect(200); + + // pattern of when the alert should fire + const pattern = { + instance: [true, true, true, true, false, true], + }; + + const response = await supertestWithoutAuth + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send( + getTestRuleData({ + rule_type_id: 'test.patternFiringAad', + schedule: { interval: '1d' }, + throttle: null, + notify_when: null, + params: { + pattern, + }, + actions: [ + { + id: createdAction.id, + group: 'default', + params: {}, + frequency: { + summary: false, + throttle: null, + notify_when: RuleNotifyWhen.CHANGE, + }, + }, + ], + alert_delay: { + active: 3, + }, + }) + ); + + expect(response.status).to.eql(200); + const ruleId = response.body.id; + objectRemover.add(Spaces.space1.id, ruleId, 'rule', 'alerting'); + + // -------------------------- + // RUN 1 - 0 new alerts + // -------------------------- + let events: IValidatedEvent[] = await waitForEventLogDocs( + ruleId, + new Map([['execute', { equal: 1 }]]) + ); + let executeEvent = events[0]; + expect(get(executeEvent, ACTIVE_PATH)).to.be(0); + expect(get(executeEvent, NEW_PATH)).to.be(0); + expect(get(executeEvent, RECOVERED_PATH)).to.be(0); + expect(get(executeEvent, ACTION_PATH)).to.be(0); + + // Query for alerts + const alertDocsRun1 = await queryForAlertDocs(); + + // Get alert state from task document + let state: any = await getTaskState(ruleId); + expect(state.alertInstances.instance.meta.activeCount).to.equal(1); + expect(state.alertInstances.instance.state.patternIndex).to.equal(0); + + // After the first run, we should have 0 alert docs for the 0 active alerts + expect(alertDocsRun1.length).to.equal(0); + + // -------------------------- + // RUN 2 - 0 new alerts + // -------------------------- + let runSoon = await supertestWithoutAuth + .post(`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rule/${ruleId}/_run_soon`) + .set('kbn-xsrf', 'foo'); + expect(runSoon.status).to.eql(204); + + events = await waitForEventLogDocs(ruleId, new Map([['execute', { equal: 2 }]])); + executeEvent = events[1]; + expect(get(executeEvent, ACTIVE_PATH)).to.be(0); + expect(get(executeEvent, NEW_PATH)).to.be(0); + expect(get(executeEvent, RECOVERED_PATH)).to.be(0); + expect(get(executeEvent, ACTION_PATH)).to.be(0); + + // Query for alerts + const alertDocsRun2 = await queryForAlertDocs(); + + // Get alert state from task document + state = await getTaskState(ruleId); + expect(state.alertInstances.instance.meta.activeCount).to.equal(2); + expect(state.alertInstances.instance.state.patternIndex).to.equal(1); + + // After the second run, we should have 0 alert docs for the 0 active alerts + expect(alertDocsRun2.length).to.equal(0); + + // -------------------------- + // RUN 3 - 1 new alert + // -------------------------- + runSoon = await supertestWithoutAuth + .post(`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rule/${ruleId}/_run_soon`) + .set('kbn-xsrf', 'foo'); + expect(runSoon.status).to.eql(204); + + events = await waitForEventLogDocs(ruleId, new Map([['execute', { equal: 3 }]])); + executeEvent = events[2]; + let executionUuid = get(executeEvent, UUID_PATH); + expect(get(executeEvent, ACTIVE_PATH)).to.be(1); + expect(get(executeEvent, NEW_PATH)).to.be(1); + expect(get(executeEvent, RECOVERED_PATH)).to.be(0); + expect(get(executeEvent, ACTION_PATH)).to.be(1); + + // Query for alerts + const alertDocsRun3 = await queryForAlertDocs(); + + // Get alert state from task document + state = await getTaskState(ruleId); + expect(state.alertInstances.instance.meta.activeCount).to.equal(3); + expect(state.alertInstances.instance.state.patternIndex).to.equal(2); + + // After the third run, we should have 1 alert docs for the 1 active alert + expect(alertDocsRun3.length).to.equal(1); + + testExpectRuleData(alertDocsRun3, ruleId, { pattern }, executionUuid!); + let source: PatternFiringAlert = alertDocsRun3[0]._source!; + + // Each doc should have active status and default action group id + expect(source[ALERT_ACTION_GROUP]).to.equal('default'); + // patternIndex should be 2 for the third run + expect(source.patternIndex).to.equal(2); + // alert UUID should equal doc id + expect(source[ALERT_UUID]).to.equal(alertDocsRun3[0]._id); + // duration should be 0 since this is a new alert + expect(source[ALERT_DURATION]).to.equal(0); + // start should be defined + expect(source[ALERT_START]).to.match(timestampPattern); + // time_range.gte should be same as start + expect(source[ALERT_TIME_RANGE]?.gte).to.equal(source[ALERT_START]); + // timestamp should be defined + expect(source['@timestamp']).to.match(timestampPattern); + // status should be active + expect(source[ALERT_STATUS]).to.equal('active'); + // workflow status should be 'open' + expect(source[ALERT_WORKFLOW_STATUS]).to.equal('open'); + // event.action should be 'open' + expect(source[EVENT_ACTION]).to.equal('open'); + // event.kind should be 'signal' + expect(source[EVENT_KIND]).to.equal('signal'); + // tags should equal rule tags because rule type doesn't set any tags + expect(source.tags).to.eql(['foo']); + + // -------------------------- + // RUN 4 - 1 active alert + // -------------------------- + runSoon = await supertestWithoutAuth + .post(`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rule/${ruleId}/_run_soon`) + .set('kbn-xsrf', 'foo'); + expect(runSoon.status).to.eql(204); + + events = await waitForEventLogDocs(ruleId, new Map([['execute', { equal: 4 }]])); + executeEvent = events[3]; + executionUuid = get(executeEvent, UUID_PATH); + expect(get(executeEvent, ACTIVE_PATH)).to.be(1); + expect(get(executeEvent, NEW_PATH)).to.be(0); + expect(get(executeEvent, RECOVERED_PATH)).to.be(0); + expect(get(executeEvent, ACTION_PATH)).to.be(0); + + // Query for alerts + const alertDocsRun4 = await queryForAlertDocs(); + + // Get alert state from task document + state = await getTaskState(ruleId); + expect(state.alertInstances.instance.meta.activeCount).to.equal(4); + expect(state.alertInstances.instance.state.patternIndex).to.equal(3); + + // After the fourth run, we should have 1 alert docs for the 1 active alert + expect(alertDocsRun4.length).to.equal(1); + + testExpectRuleData(alertDocsRun4, ruleId, { pattern }, executionUuid!); + source = alertDocsRun4[0]._source!; + const run3Source = alertDocsRun3[0]._source!; + + expect(source[ALERT_UUID]).to.equal(run3Source[ALERT_UUID]); + // patternIndex should be 3 for the fourth run + expect(source.patternIndex).to.equal(3); + expect(source[ALERT_ACTION_GROUP]).to.equal('default'); + // start time should be defined and the same as prior run + expect(source[ALERT_START]).to.match(timestampPattern); + expect(source[ALERT_START]).to.equal(run3Source[ALERT_START]); + // timestamp should be defined and not the same as prior run + expect(source['@timestamp']).to.match(timestampPattern); + expect(source['@timestamp']).not.to.equal(run3Source['@timestamp']); + // status should still be active + expect(source[ALERT_STATUS]).to.equal('active'); + // event.action set to active + expect(source[EVENT_ACTION]).to.eql('active'); + expect(source.tags).to.eql(['foo']); + // these values should be the same as previous run + expect(source[EVENT_KIND]).to.eql(run3Source[EVENT_KIND]); + expect(source[ALERT_WORKFLOW_STATUS]).to.eql(run3Source[ALERT_WORKFLOW_STATUS]); + expect(source[ALERT_TIME_RANGE]?.gte).to.equal(run3Source[ALERT_TIME_RANGE]?.gte); + + // -------------------------- + // RUN 5 - 1 recovered alert + // -------------------------- + runSoon = await supertestWithoutAuth + .post(`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rule/${ruleId}/_run_soon`) + .set('kbn-xsrf', 'foo'); + expect(runSoon.status).to.eql(204); + + events = await waitForEventLogDocs(ruleId, new Map([['execute', { equal: 5 }]])); + executeEvent = events[4]; + executionUuid = get(executeEvent, UUID_PATH); + expect(get(executeEvent, ACTIVE_PATH)).to.be(0); + expect(get(executeEvent, NEW_PATH)).to.be(0); + expect(get(executeEvent, RECOVERED_PATH)).to.be(1); + expect(get(executeEvent, ACTION_PATH)).to.be(0); + + // Query for alerts + const alertDocsRun5 = await queryForAlertDocs(); + + // Get alert state from task document + state = await getTaskState(ruleId); + expect(state.alertRecoveredInstances.instance.meta.activeCount).to.equal(0); + + // After the fourth run, we should have 1 alert docs for the 1 recovered alert + expect(alertDocsRun5.length).to.equal(1); + + testExpectRuleData(alertDocsRun5, ruleId, { pattern }, executionUuid!); + source = alertDocsRun5[0]._source!; + + // action group should be set to recovered + expect(source[ALERT_ACTION_GROUP]).to.be('recovered'); + // rule type AAD payload should be set to recovery values + expect(source.instancePattern).to.eql([]); + expect(source.patternIndex).to.eql(-1); + // uuid is the same + expect(source[ALERT_UUID]).to.equal(run3Source[ALERT_UUID]); + // start time should be defined and the same as before + expect(source[ALERT_START]).to.match(timestampPattern); + expect(source[ALERT_START]).to.equal(run3Source[ALERT_START]); + // timestamp should be defined and not the same as prior run + expect(source['@timestamp']).to.match(timestampPattern); + expect(source['@timestamp']).not.to.equal(run3Source['@timestamp']); + // end time should be defined + expect(source[ALERT_END]).to.match(timestampPattern); + // status should be set to recovered + expect(source[ALERT_STATUS]).to.equal('recovered'); + // event.action set to close + expect(source[EVENT_ACTION]).to.eql('close'); + expect(source.tags).to.eql(['foo']); + // these values should be the same as previous run + expect(source[EVENT_KIND]).to.eql(run3Source[EVENT_KIND]); + expect(source[ALERT_WORKFLOW_STATUS]).to.eql(run3Source[ALERT_WORKFLOW_STATUS]); + expect(source[ALERT_TIME_RANGE]?.gte).to.equal(run3Source[ALERT_TIME_RANGE]?.gte); + // time_range.lte should be set to end time + expect(source[ALERT_TIME_RANGE]?.lte).to.equal(source[ALERT_END]); + + // -------------------------- + // RUN 6 - 0 new alerts + // -------------------------- + runSoon = await supertestWithoutAuth + .post(`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rule/${ruleId}/_run_soon`) + .set('kbn-xsrf', 'foo'); + expect(runSoon.status).to.eql(204); + + events = await waitForEventLogDocs(ruleId, new Map([['execute', { equal: 6 }]])); + executeEvent = events[5]; + expect(get(executeEvent, ACTIVE_PATH)).to.be(0); + expect(get(executeEvent, NEW_PATH)).to.be(0); + expect(get(executeEvent, RECOVERED_PATH)).to.be(0); + expect(get(executeEvent, ACTION_PATH)).to.be(0); + + // Query for alerts + const alertDocsRun6 = await queryForAlertDocs(); + + // Get alert state from task document + state = await getTaskState(ruleId); + expect(state.alertInstances.instance.meta.activeCount).to.equal(1); + expect(state.alertInstances.instance.state.patternIndex).to.equal(5); + + // After the sixth run, we should have 1 alert docs for the previously recovered alert + expect(alertDocsRun6.length).to.equal(1); + }); + + it('should generate expected events with a alertDelay with AAD (rule registry)', async () => { + const params = { + index: ES_TEST_INDEX_NAME, + reference: 'test', + }; + const { body: createdAction } = await supertestWithoutAuth + .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'MY action', + connector_type_id: 'test.noop', + config: {}, + secrets: {}, + }) + .expect(200); + + const response = await supertestWithoutAuth + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send( + getTestRuleData({ + rule_type_id: 'test.always-firing-alert-as-data', + schedule: { interval: '1d' }, + throttle: null, + notify_when: null, + params, + actions: [ + { + id: createdAction.id, + group: 'default', + params: {}, + frequency: { + summary: false, + throttle: null, + notify_when: RuleNotifyWhen.CHANGE, + }, + }, + ], + alert_delay: { + active: 3, + }, + }) + ); + + expect(response.status).to.eql(200); + const ruleId = response.body.id; + objectRemover.add(Spaces.space1.id, ruleId, 'rule', 'alerting'); + + // -------------------------- + // RUN 1 - 0 new alerts + // -------------------------- + let events: IValidatedEvent[] = await waitForEventLogDocs( + ruleId, + new Map([['execute', { equal: 1 }]]) + ); + let executeEvent = events[0]; + expect(get(executeEvent, ACTIVE_PATH)).to.be(0); + expect(get(executeEvent, NEW_PATH)).to.be(0); + expect(get(executeEvent, RECOVERED_PATH)).to.be(0); + expect(get(executeEvent, ACTION_PATH)).to.be(0); + + // Query for alerts + const alertDocsRun1 = await queryForAlertDocs(alwaysFiringAlertsAsDataIndex); + + // Get alert state from task document + let state: any = await getTaskState(ruleId); + expect(state.alertInstances['1'].meta.activeCount).to.equal(1); + expect(state.alertTypeState.trackedAlerts['1'].activeCount).to.equal(1); + + // After the first run, we should have 0 alert docs for the 0 active alerts + expect(alertDocsRun1.length).to.equal(0); + + // -------------------------- + // RUN 2 - 0 new alerts + // -------------------------- + let runSoon = await supertestWithoutAuth + .post(`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rule/${ruleId}/_run_soon`) + .set('kbn-xsrf', 'foo'); + expect(runSoon.status).to.eql(204); + + events = await waitForEventLogDocs(ruleId, new Map([['execute', { equal: 2 }]])); + executeEvent = events[1]; + expect(get(executeEvent, ACTIVE_PATH)).to.be(0); + expect(get(executeEvent, NEW_PATH)).to.be(0); + expect(get(executeEvent, RECOVERED_PATH)).to.be(0); + expect(get(executeEvent, ACTION_PATH)).to.be(0); + + // Query for alerts + const alertDocsRun2 = await queryForAlertDocs(alwaysFiringAlertsAsDataIndex); + + // Get alert state from task document + state = await getTaskState(ruleId); + expect(state.alertInstances['1'].meta.activeCount).to.equal(2); + expect(state.alertTypeState.trackedAlerts['1'].activeCount).to.equal(2); + + // After the second run, we should have 0 alert docs for the 0 active alerts + expect(alertDocsRun2.length).to.equal(0); + + // -------------------------- + // RUN 3 - 1 new alert + // -------------------------- + runSoon = await supertestWithoutAuth + .post(`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rule/${ruleId}/_run_soon`) + .set('kbn-xsrf', 'foo'); + expect(runSoon.status).to.eql(204); + + events = await waitForEventLogDocs(ruleId, new Map([['execute', { equal: 3 }]])); + executeEvent = events[2]; + let executionUuid = get(executeEvent, UUID_PATH); + // Note: the rule creates 2 alerts but we will only look at one + expect(get(executeEvent, ACTIVE_PATH)).to.be(2); + expect(get(executeEvent, NEW_PATH)).to.be(2); + expect(get(executeEvent, RECOVERED_PATH)).to.be(0); + expect(get(executeEvent, ACTION_PATH)).to.be(2); + + // Query for alerts + const alertDocsRun3 = await queryForAlertDocs(alwaysFiringAlertsAsDataIndex); + + // Get alert state from task document + state = await getTaskState(ruleId); + expect(state.alertInstances['1'].meta.activeCount).to.equal(3); + expect(state.alertTypeState.trackedAlerts['1'].activeCount).to.equal(3); + + // After the third run, we should have 2 alert docs for the 2 active alerts but we will only look at one + expect(alertDocsRun3.length).to.equal(2); + + let source: Alert = alertDocsRun3[0]._source!; + + // Each doc should have a copy of the rule data + expect(source[ALERT_RULE_CATEGORY]).to.equal('Test: Always Firing Alert As Data'); + expect(source[ALERT_RULE_CONSUMER]).to.equal('alertsFixture'); + expect(source[ALERT_RULE_NAME]).to.equal('abc'); + expect(source[ALERT_RULE_PRODUCER]).to.equal('alertsFixture'); + expect(source[ALERT_RULE_TAGS]).to.eql(['foo']); + expect(source[ALERT_RULE_TYPE_ID]).to.equal('test.always-firing-alert-as-data'); + expect(source[ALERT_RULE_UUID]).to.equal(ruleId); + expect(source[ALERT_RULE_PARAMETERS]).to.eql(params); + expect(source[SPACE_IDS]).to.eql(['space1']); + expect(source[ALERT_RULE_EXECUTION_UUID]).to.equal(executionUuid); + // alert UUID should equal doc id + expect(source[ALERT_UUID]).to.equal(alertDocsRun3[0]._id); + // duration should be 0 since this is a new alert + expect(source[ALERT_DURATION]).to.equal(0); + // start should be defined + expect(source[ALERT_START]).to.match(timestampPattern); + // time_range.gte should be same as start + expect(source[ALERT_TIME_RANGE]?.gte).to.equal(source[ALERT_START]); + // timestamp should be defined + expect(source['@timestamp']).to.match(timestampPattern); + // status should be active + expect(source[ALERT_STATUS]).to.equal('active'); + // workflow status should be 'open' + expect(source[ALERT_WORKFLOW_STATUS]).to.equal('open'); + // event.action should be 'open' + expect(source[EVENT_ACTION]).to.equal('open'); + // event.kind should be 'signal' + expect(source[EVENT_KIND]).to.equal('signal'); + // tags should equal rule tags because rule type doesn't set any tags + expect(source.tags).to.eql(['foo']); + + // -------------------------- + // RUN 4 - 1 active alert + // -------------------------- + runSoon = await supertestWithoutAuth + .post(`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rule/${ruleId}/_run_soon`) + .set('kbn-xsrf', 'foo'); + expect(runSoon.status).to.eql(204); + + events = await waitForEventLogDocs(ruleId, new Map([['execute', { equal: 4 }]])); + executeEvent = events[3]; + executionUuid = get(executeEvent, UUID_PATH); + // Note: the rule creates 2 alerts but we will only look at one + expect(get(executeEvent, ACTIVE_PATH)).to.be(2); + expect(get(executeEvent, NEW_PATH)).to.be(0); + expect(get(executeEvent, RECOVERED_PATH)).to.be(0); + expect(get(executeEvent, ACTION_PATH)).to.be(0); + + // Query for alerts + const alertDocsRun4 = await queryForAlertDocs(alwaysFiringAlertsAsDataIndex); + + // Get alert state from task document + state = await getTaskState(ruleId); + expect(state.alertInstances['1'].meta.activeCount).to.equal(4); + expect(state.alertTypeState.trackedAlerts['1'].activeCount).to.equal(4); + + // After the fourth run, we should have 2 alert docs for the 2 active alerts but we will only look at one + expect(alertDocsRun4.length).to.equal(2); + + source = alertDocsRun4[0]._source!; + const run3Source = alertDocsRun3[0]._source!; + + // Each doc should have a copy of the rule data + expect(source[ALERT_RULE_CATEGORY]).to.equal('Test: Always Firing Alert As Data'); + expect(source[ALERT_RULE_CONSUMER]).to.equal('alertsFixture'); + expect(source[ALERT_RULE_NAME]).to.equal('abc'); + expect(source[ALERT_RULE_PRODUCER]).to.equal('alertsFixture'); + expect(source[ALERT_RULE_TAGS]).to.eql(['foo']); + expect(source[ALERT_RULE_TYPE_ID]).to.equal('test.always-firing-alert-as-data'); + expect(source[ALERT_RULE_UUID]).to.equal(ruleId); + expect(source[ALERT_RULE_PARAMETERS]).to.eql(params); + expect(source[SPACE_IDS]).to.eql(['space1']); + expect(source[ALERT_RULE_EXECUTION_UUID]).to.equal(executionUuid); + expect(source[ALERT_UUID]).to.equal(run3Source[ALERT_UUID]); + // start time should be defined and the same as prior run + expect(source[ALERT_START]).to.match(timestampPattern); + expect(source[ALERT_START]).to.equal(run3Source[ALERT_START]); + // timestamp should be defined and not the same as prior run + expect(source['@timestamp']).to.match(timestampPattern); + expect(source['@timestamp']).not.to.equal(run3Source['@timestamp']); + // status should still be active + expect(source[ALERT_STATUS]).to.equal('active'); + // event.action set to active + expect(source[EVENT_ACTION]).to.eql('active'); + expect(source.tags).to.eql(['foo']); + // these values should be the same as previous run + expect(source[EVENT_KIND]).to.eql(run3Source[EVENT_KIND]); + expect(source[ALERT_WORKFLOW_STATUS]).to.eql(run3Source[ALERT_WORKFLOW_STATUS]); + expect(source[ALERT_TIME_RANGE]?.gte).to.equal(run3Source[ALERT_TIME_RANGE]?.gte); + }); + }); + + function testExpectRuleData( + alertDocs: Array>, + ruleId: string, + ruleParameters: unknown, + executionUuid?: string + ) { + for (let i = 0; i < alertDocs.length; ++i) { + const source: PatternFiringAlert = alertDocs[i]._source!; + + // Each doc should have a copy of the rule data + expect(source[ALERT_RULE_CATEGORY]).to.equal( + 'Test: Firing on a Pattern and writing Alerts as Data' + ); + expect(source[ALERT_RULE_CONSUMER]).to.equal('alertsFixture'); + expect(source[ALERT_RULE_NAME]).to.equal('abc'); + expect(source[ALERT_RULE_PRODUCER]).to.equal('alertsFixture'); + expect(source[ALERT_RULE_TAGS]).to.eql(['foo']); + expect(source[ALERT_RULE_TYPE_ID]).to.equal('test.patternFiringAad'); + expect(source[ALERT_RULE_UUID]).to.equal(ruleId); + expect(source[ALERT_RULE_PARAMETERS]).to.eql(ruleParameters); + expect(source[SPACE_IDS]).to.eql(['space1']); + + if (executionUuid) { + expect(source[ALERT_RULE_EXECUTION_UUID]).to.equal(executionUuid); + } + } + } + + async function queryForAlertDocs( + index: string = alertsAsDataIndex + ): Promise>> { + const searchResult = await es.search({ + index, + body: { query: { match_all: {} } }, + }); + return searchResult.hits.hits as Array>; + } + + async function getTaskState(ruleId: string) { + const task = await es.get({ + id: `task:${ruleId}`, + index: '.kibana_task_manager', + }); + + return JSON.parse(task._source!.task.state); + } + + async function waitForEventLogDocs( + id: string, + actions: Map + ) { + return await retry.try(async () => { + return await getEventLog({ + getService, + spaceId: Spaces.space1.id, + type: 'alert', + id, + provider: 'alerting', + actions, + }); + }); + } +} diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data/index.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data/index.ts index 20342e053016d..e1a29d1c4bf3e 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data/index.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data/index.ts @@ -14,5 +14,6 @@ export default function alertsAsDataTests({ loadTestFile }: FtrProviderContext) loadTestFile(require.resolve('./alerts_as_data')); loadTestFile(require.resolve('./alerts_as_data_flapping')); loadTestFile(require.resolve('./alerts_as_data_conflicts')); + loadTestFile(require.resolve('./alerts_as_data_alert_delay')); }); } diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/index.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/index.ts index b73477cf3df30..15084a47f4d86 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/index.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/index.ts @@ -28,7 +28,7 @@ export default function alertingTests({ loadTestFile, getService }: FtrProviderC loadTestFile(require.resolve('./run_soon')); loadTestFile(require.resolve('./flapping_history')); loadTestFile(require.resolve('./check_registered_rule_types')); - loadTestFile(require.resolve('./notification_delay')); + loadTestFile(require.resolve('./alert_delay')); loadTestFile(require.resolve('./generate_alert_schemas')); // Do not place test files here, due to https://github.com/elastic/kibana/issues/123059 diff --git a/x-pack/test/api_integration/apis/management/index_management/component_templates.ts b/x-pack/test/api_integration/apis/management/index_management/component_templates.ts index f41dedccb126f..3f2f9310faa09 100644 --- a/x-pack/test/api_integration/apis/management/index_management/component_templates.ts +++ b/x-pack/test/api_integration/apis/management/index_management/component_templates.ts @@ -92,6 +92,7 @@ export default function ({ getService }: FtrProviderContext) { usedBy: [], isManaged: false, hasSettings: true, + isDeprecated: false, hasMappings: true, hasAliases: false, }); @@ -103,6 +104,7 @@ export default function ({ getService }: FtrProviderContext) { const { body } = await getOneComponentTemplate(COMPONENT_NAME).expect(200); expect(body).to.eql({ + isDeprecated: false, name: COMPONENT_NAME, ...COMPONENT, _kbnMeta: { diff --git a/x-pack/test/api_integration/apis/maps/bsearch.ts b/x-pack/test/api_integration/apis/maps/bsearch.ts new file mode 100644 index 0000000000000..1813bcd0675c5 --- /dev/null +++ b/x-pack/test/api_integration/apis/maps/bsearch.ts @@ -0,0 +1,112 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import request from 'superagent'; +import { inflateResponse } from '@kbn/bfetch-plugin/public/streaming'; +import expect from '@kbn/expect'; +import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common'; +import { BFETCH_ROUTE_VERSION_LATEST } from '@kbn/bfetch-plugin/common'; +import type { FtrProviderContext } from '../../ftr_provider_context'; + +function parseBfetchResponse(resp: request.Response, compressed: boolean = false) { + return resp.text + .trim() + .split('\n') + .map((item) => { + return JSON.parse(compressed ? inflateResponse(item) : item); + }); +} + +export default function ({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + + describe('bsearch', () => { + describe('ES|QL', () => { + it(`should return getColumns response in expected shape`, async () => { + const resp = await supertest + .post(`/internal/bsearch`) + .set('kbn-xsrf', 'kibana') + .set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST) + .send({ + batch: [ + { + request: { + params: { + query: 'from logstash-* | keep geo.coordinates | limit 0', + }, + }, + options: { + strategy: 'esql', + }, + }, + ], + }); + + const jsonBody = parseBfetchResponse(resp); + expect(resp.status).to.be(200); + expect(jsonBody[0].result.rawResponse).to.eql({ + columns: [ + { + name: 'geo.coordinates', + type: 'geo_point', + }, + ], + values: [], + }); + }); + + it(`should return getValues response in expected shape`, async () => { + const resp = await supertest + .post(`/internal/bsearch`) + .set('kbn-xsrf', 'kibana') + .set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST) + .send({ + batch: [ + { + request: { + params: { + dropNullColumns: true, + query: + 'from logstash-* | keep geo.coordinates, @timestamp | sort @timestamp | limit 1', + }, + }, + options: { + strategy: 'esql', + }, + }, + ], + }); + + const jsonBody = parseBfetchResponse(resp); + expect(resp.status).to.be(200); + expect(jsonBody[0].result.rawResponse).to.eql({ + all_columns: [ + { + name: 'geo.coordinates', + type: 'geo_point', + }, + { + name: '@timestamp', + type: 'date', + }, + ], + columns: [ + { + name: 'geo.coordinates', + type: 'geo_point', + }, + { + name: '@timestamp', + type: 'date', + }, + ], + values: [['POINT (-120.9871642 38.68407028)', '2015-09-20T00:00:00.000Z']], + }); + }); + }); + }); +} diff --git a/x-pack/test/api_integration/apis/maps/index.js b/x-pack/test/api_integration/apis/maps/index.js index 438b37ae841c9..fec2cac61950b 100644 --- a/x-pack/test/api_integration/apis/maps/index.js +++ b/x-pack/test/api_integration/apis/maps/index.js @@ -38,6 +38,7 @@ export default function ({ loadTestFile, getService }) { loadTestFile(require.resolve('./migrations')); loadTestFile(require.resolve('./get_tile')); loadTestFile(require.resolve('./get_grid_tile')); + loadTestFile(require.resolve('./bsearch')); }); }); } diff --git a/x-pack/test/api_integration/apis/search/search.ts b/x-pack/test/api_integration/apis/search/search.ts index 15c774eef34ef..bccc7321f8948 100644 --- a/x-pack/test/api_integration/apis/search/search.ts +++ b/x-pack/test/api_integration/apis/search/search.ts @@ -269,26 +269,6 @@ export default function ({ getService }: FtrProviderContext) { verifyErrorResponse(resp.body, 400, 'Request must contain a kbn-xsrf header.'); }); - it('should return 400 when unknown index type is provided', async () => { - const resp = await supertest - .post(`/internal/search/ese`) - .set(ELASTIC_HTTP_VERSION_HEADER, '1') - .set('kbn-xsrf', 'foo') - .send({ - indexType: 'baad', - params: { - body: { - query: { - match_all: {}, - }, - }, - }, - }) - .expect(400); - - verifyErrorResponse(resp.body, 400, 'Unknown indexType'); - }); - it('should return 400 if invalid id is provided', async () => { const resp = await supertest .post(`/internal/search/ese/123`) diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/attachments_framework/external_references.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/attachments_framework/external_references.ts index 53b4e7d94c955..8ae71534ce3cd 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/attachments_framework/external_references.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/attachments_framework/external_references.ts @@ -501,6 +501,7 @@ export default ({ getService }: FtrProviderContext): void => { expect(types).to.eql({ '.files': '559a37324c84f1f2eadcc5bce43115d09501ffe4', '.test': 'ab2204830c67f5cf992c9aa2f7e3ead752cc60a1', + endpoint: 'e13fe41b5c330dd923da91992ed0cedb7e30960f', indicator: 'e1ea6f0518f2e0e4b0b5c0739efe805598cf2516', osquery: '99bee68fce8ee84e81d67c536e063d3e1a2cee96', }); diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/patch_cases.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/patch_cases.ts index c479c2f5398b7..cc49ddf44bdcc 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/patch_cases.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/patch_cases.ts @@ -11,6 +11,7 @@ import { ALERT_WORKFLOW_STATUS } from '@kbn/rule-data-utils'; import { DETECTION_ENGINE_QUERY_SIGNALS_URL } from '@kbn/security-solution-plugin/common/constants'; import { AttachmentType, + CaseCustomFields, Cases, CaseSeverity, CaseStatuses, @@ -330,6 +331,7 @@ export default ({ getService }: FtrProviderContext): void => { key: 'test_custom_field_2', label: 'toggle', type: CustomFieldTypes.TOGGLE, + defaultValue: false, required: true, }, ], @@ -401,6 +403,7 @@ export default ({ getService }: FtrProviderContext): void => { key: 'test_custom_field_2', label: 'toggle', type: CustomFieldTypes.TOGGLE, + defaultValue: false, required: true, }, ], @@ -1084,51 +1087,123 @@ export default ({ getService }: FtrProviderContext): void => { }); describe('customFields', async () => { - it('400s when trying to patch with duplicated custom field keys', async () => { - const postedCase = await createCase(supertest, postCaseReq); + it('patches a case with missing required custom fields to their default values', async () => { + await createConfiguration( + supertest, + getConfigurationRequest({ + overrides: { + customFields: [ + { + key: 'text_custom_field', + label: 'text', + type: CustomFieldTypes.TEXT, + defaultValue: 'default value', + required: true, + }, + { + key: 'toggle_custom_field', + label: 'toggle', + type: CustomFieldTypes.TOGGLE, + defaultValue: false, + required: true, + }, + ], + }, + }) + ); - await updateCase({ + const originalValues = [ + { + key: 'text_custom_field', + type: CustomFieldTypes.TEXT, + value: 'hello', + }, + { + key: 'toggle_custom_field', + type: CustomFieldTypes.TOGGLE, + value: true, + }, + ] as CaseCustomFields; + + const postedCase = await createCase(supertest, { + ...postCaseReq, + customFields: originalValues, + }); + + const patchedCases = await updateCase({ supertest, params: { cases: [ { id: postedCase.id, version: postedCase.version, - customFields: [ - { - key: 'duplicated_key', - type: CustomFieldTypes.TEXT, - value: 'this is a text field value', - }, - { - key: 'duplicated_key', - type: CustomFieldTypes.TEXT, - value: 'this is a text field value', - }, - ], + customFields: [], }, ], }, - expectedHttpCode: 400, }); + + expect(patchedCases[0].customFields).to.eql([ + { ...originalValues[0], value: 'default value' }, + { ...originalValues[1], value: false }, + ]); }); - it('400s when trying to patch with a custom field key that does not exist', async () => { + it('400s trying to patch a case with missing required custom fields if they dont have default values', async () => { await createConfiguration( supertest, getConfigurationRequest({ overrides: { customFields: [ { - key: 'test_custom_field', + key: 'text_custom_field', label: 'text', type: CustomFieldTypes.TEXT, - required: false, + required: true, + }, + { + key: 'toggle_custom_field', + label: 'toggle', + type: CustomFieldTypes.TOGGLE, + required: true, }, ], }, }) ); + + const postedCase = await createCase(supertest, { + ...postCaseReq, + customFields: [ + { + key: 'text_custom_field', + type: CustomFieldTypes.TEXT, + value: 'hello', + }, + { + key: 'toggle_custom_field', + type: CustomFieldTypes.TOGGLE, + value: true, + }, + ], + }); + + await updateCase({ + supertest, + params: { + cases: [ + { + id: postedCase.id, + version: postedCase.version, + customFields: [], + }, + ], + }, + expectedHttpCode: 400, + }); + }); + + it('400s when trying to patch with duplicated custom field keys', async () => { const postedCase = await createCase(supertest, postCaseReq); await updateCase({ @@ -1140,7 +1215,12 @@ export default ({ getService }: FtrProviderContext): void => { version: postedCase.version, customFields: [ { - key: 'key_does_not_exist', + key: 'duplicated_key', + type: CustomFieldTypes.TEXT, + value: 'this is a text field value', + }, + { + key: 'duplicated_key', type: CustomFieldTypes.TEXT, value: 'this is a text field value', }, @@ -1152,7 +1232,7 @@ export default ({ getService }: FtrProviderContext): void => { }); }); - it('400s when trying to patch a case with a missing required custom field', async () => { + it('400s when trying to patch with a custom field key that does not exist', async () => { await createConfiguration( supertest, getConfigurationRequest({ @@ -1162,23 +1242,13 @@ export default ({ getService }: FtrProviderContext): void => { key: 'test_custom_field', label: 'text', type: CustomFieldTypes.TEXT, - required: true, + required: false, }, ], }, }) ); - - const postedCase = await createCase(supertest, { - ...postCaseReq, - customFields: [ - { - key: 'test_custom_field', - type: CustomFieldTypes.TEXT, - value: 'hello', - }, - ], - }); + const postedCase = await createCase(supertest, postCaseReq); await updateCase({ supertest, @@ -1187,7 +1257,13 @@ export default ({ getService }: FtrProviderContext): void => { { id: postedCase.id, version: postedCase.version, - customFields: [], + customFields: [ + { + key: 'key_does_not_exist', + type: CustomFieldTypes.TEXT, + value: 'this is a text field value', + }, + ], }, ], }, @@ -1195,17 +1271,25 @@ export default ({ getService }: FtrProviderContext): void => { }); }); - it('400s when trying to patch a case with a required custom field with null value', async () => { + it('400s trying to patch required custom fields with value: null', async () => { await createConfiguration( supertest, getConfigurationRequest({ overrides: { customFields: [ { - key: 'test_custom_field', + key: 'text_custom_field', label: 'text', type: CustomFieldTypes.TEXT, required: true, + defaultValue: 'default value', + }, + { + key: 'toggle_custom_field', + label: 'toggle', + type: CustomFieldTypes.TOGGLE, + required: true, + defaultValue: false, }, ], }, @@ -1216,13 +1300,31 @@ export default ({ getService }: FtrProviderContext): void => { ...postCaseReq, customFields: [ { - key: 'test_custom_field', + key: 'text_custom_field', type: CustomFieldTypes.TEXT, - value: 'hello', + value: 'not default', + }, + { + key: 'toggle_custom_field', + type: CustomFieldTypes.TOGGLE, + value: true, }, ], }); + const patchedCustomFields = [ + { + key: 'text_custom_field', + type: CustomFieldTypes.TEXT, + value: null, + }, + { + key: 'toggle_custom_field', + type: CustomFieldTypes.TOGGLE, + value: null, + }, + ]; + await updateCase({ supertest, params: { @@ -1230,19 +1332,14 @@ export default ({ getService }: FtrProviderContext): void => { { id: postedCase.id, version: postedCase.version, - customFields: [ - { - key: 'test_custom_field', - type: CustomFieldTypes.TEXT, - value: null, - }, - ], + customFields: patchedCustomFields, }, ], }, expectedHttpCode: 400, }); }); + it('400s when trying to patch a case with a custom field with the wrong type', async () => { await createConfiguration( supertest, diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/post_case.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/post_case.ts index f512a67c95d19..5049c04b060a8 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/post_case.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/post_case.ts @@ -189,6 +189,7 @@ export default ({ getService }: FtrProviderContext): void => { key: 'valid_key_2', label: 'toggle', type: CustomFieldTypes.TOGGLE, + defaultValue: false, required: true, }, ], @@ -244,6 +245,7 @@ export default ({ getService }: FtrProviderContext): void => { key: 'valid_key_2', label: 'toggle', type: CustomFieldTypes.TOGGLE, + defaultValue: false, required: true, }, ], @@ -269,6 +271,53 @@ export default ({ getService }: FtrProviderContext): void => { { key: 'valid_key_1', type: 'text', value: null }, ]); }); + + it('creates a case with missing required custom fields and default values', async () => { + const customFieldsConfiguration = [ + { + key: 'text_custom_field', + label: 'text', + type: CustomFieldTypes.TEXT, + required: true, + defaultValue: 'default value', + }, + { + key: 'toggle_custom_field', + label: 'toggle', + type: CustomFieldTypes.TOGGLE, + defaultValue: false, + required: true, + }, + ]; + + await createConfiguration( + supertest, + getConfigurationRequest({ + overrides: { + customFields: customFieldsConfiguration, + }, + }) + ); + const createdCase = await createCase( + supertest, + getPostCaseRequest({ + customFields: [], + }) + ); + + expect(createdCase.customFields).to.eql([ + { + key: customFieldsConfiguration[0].key, + type: customFieldsConfiguration[0].type, + value: 'default value', + }, + { + key: customFieldsConfiguration[1].key, + type: customFieldsConfiguration[1].type, + value: false, + }, + ]); + }); }); describe('unhappy path', () => { @@ -482,28 +531,33 @@ export default ({ getService }: FtrProviderContext): void => { ); }); - it('400s when creating a case with a missing required custom field', async () => { + it('400s trying to create a case with required custom fields set to null', async () => { + const customFieldsConfiguration = [ + { + key: 'text_custom_field', + label: 'text', + type: CustomFieldTypes.TEXT, + required: true, + defaultValue: 'default value', + }, + { + key: 'toggle_custom_field', + label: 'toggle', + type: CustomFieldTypes.TOGGLE, + defaultValue: false, + required: true, + }, + ]; + await createConfiguration( supertest, getConfigurationRequest({ overrides: { - customFields: [ - { - key: 'text_custom_field', - label: 'text', - type: CustomFieldTypes.TEXT, - required: false, - }, - { - key: 'toggle_custom_field', - label: 'toggle', - type: CustomFieldTypes.TOGGLE, - required: true, - }, - ], + customFields: customFieldsConfiguration, }, }) ); + await createCase( supertest, getPostCaseRequest({ @@ -511,37 +565,11 @@ export default ({ getService }: FtrProviderContext): void => { { key: 'text_custom_field', type: CustomFieldTypes.TEXT, - value: 'a', + value: null, }, - ], - }), - 400 - ); - }); - - it('400s when trying to create case with a required custom field as null', async () => { - await createConfiguration( - supertest, - getConfigurationRequest({ - overrides: { - customFields: [ - { - key: 'text_custom_field', - label: 'text', - type: CustomFieldTypes.TEXT, - required: true, - }, - ], - }, - }) - ); - await createCase( - supertest, - getPostCaseRequest({ - customFields: [ { - key: 'text_custom_field', - type: CustomFieldTypes.TEXT, + key: 'toggle_custom_field', + type: CustomFieldTypes.TOGGLE, value: null, }, ], @@ -560,6 +588,7 @@ export default ({ getService }: FtrProviderContext): void => { key: 'test_custom_field', label: 'text', type: CustomFieldTypes.TEXT, + defaultValue: 'foobar', required: true, }, ], diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/configure/get_configure.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/configure/get_configure.ts index 87c65ba8a47c9..4a9e99016c801 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/configure/get_configure.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/configure/get_configure.ts @@ -59,7 +59,13 @@ export default ({ getService }: FtrProviderContext): void => { const customFields = { customFields: [ { key: 'hello', label: 'text', type: CustomFieldTypes.TEXT, required: false }, - { key: 'goodbye', label: 'toggle', type: CustomFieldTypes.TOGGLE, required: true }, + { + key: 'goodbye', + label: 'toggle', + type: CustomFieldTypes.TOGGLE, + required: true, + defaultValue: false, + }, ], }; await createConfiguration( diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/configure/patch_configure.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/configure/patch_configure.ts index 407529738b488..d5b6e931ca671 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/configure/patch_configure.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/configure/patch_configure.ts @@ -7,6 +7,7 @@ import expect from '@kbn/expect'; import { ConnectorTypes, CustomFieldTypes } from '@kbn/cases-plugin/common/types/domain'; +import { ConfigurationPatchRequest } from '@kbn/cases-plugin/common/types/api'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; import { ObjectRemover as ActionsRemover } from '../../../../../alerting_api_integration/common/lib'; @@ -57,10 +58,10 @@ export default ({ getService }: FtrProviderContext): void => { it('should patch a configuration with customFields', async () => { const customFields = [ { - key: 'text_field', + key: 'text_field_1', label: '#1', type: CustomFieldTypes.TEXT, - required: false, + required: true, }, { key: 'toggle_field', @@ -68,7 +69,14 @@ export default ({ getService }: FtrProviderContext): void => { type: CustomFieldTypes.TOGGLE, required: false, }, - ]; + { + key: 'text_field_2', + label: '#3', + type: CustomFieldTypes.TEXT, + required: true, + defaultValue: 'foobar', + }, + ] as ConfigurationPatchRequest['customFields']; const configuration = await createConfiguration(supertest); const newConfiguration = await updateConfiguration(supertest, configuration.id, { version: configuration.version, diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/configure/post_configure.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/configure/post_configure.ts index 02721521a2e4f..15efb00444993 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/configure/post_configure.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/configure/post_configure.ts @@ -64,7 +64,19 @@ export default ({ getService }: FtrProviderContext): void => { const customFields = { customFields: [ { key: 'hello', label: 'text', type: CustomFieldTypes.TEXT, required: false }, - { key: 'goodbye', label: 'toggle', type: CustomFieldTypes.TOGGLE, required: true }, + { + key: 'goodbye', + label: 'toggle', + type: CustomFieldTypes.TOGGLE, + required: true, + defaultValue: false, + }, + { + key: 'hello_again', + label: 'text', + type: CustomFieldTypes.TEXT, + required: true, + }, ], }; diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/internal/search_cases.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/internal/search_cases.ts index 563a1d2ec83dc..5f5e9af85e631 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/internal/search_cases.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/internal/search_cases.ts @@ -62,6 +62,7 @@ export default ({ getService }: FtrProviderContext): void => { label: 'toggle', type: CustomFieldTypes.TOGGLE, required: true, + defaultValue: false, }, ], }, @@ -130,6 +131,7 @@ export default ({ getService }: FtrProviderContext): void => { label: 'toggle', type: CustomFieldTypes.TOGGLE, required: true, + defaultValue: false, }, { key: 'valid_key_3', @@ -217,6 +219,7 @@ export default ({ getService }: FtrProviderContext): void => { label: 'toggle', type: CustomFieldTypes.TOGGLE, required: true, + defaultValue: false, }, { key: 'valid_key_3', @@ -245,6 +248,7 @@ export default ({ getService }: FtrProviderContext): void => { label: 'toggle', type: CustomFieldTypes.TOGGLE, required: true, + defaultValue: false, }, { key: 'valid_obs_key_3', @@ -344,6 +348,7 @@ export default ({ getService }: FtrProviderContext): void => { label: 'toggle', type: CustomFieldTypes.TOGGLE, required: true, + defaultValue: false, }, ], }, @@ -419,6 +424,7 @@ export default ({ getService }: FtrProviderContext): void => { label: 'toggle', type: CustomFieldTypes.TOGGLE, required: true, + defaultValue: false, }, ], owner: 'securitySolutionFixture', @@ -450,6 +456,7 @@ export default ({ getService }: FtrProviderContext): void => { label: 'toggle', type: CustomFieldTypes.TOGGLE, required: true, + defaultValue: false, }, ], owner: 'securitySolutionFixture', @@ -473,6 +480,7 @@ export default ({ getService }: FtrProviderContext): void => { label: 'toggle', type: CustomFieldTypes.TOGGLE, required: true, + defaultValue: false, }, { key: 'valid_obs_key_3', @@ -513,6 +521,7 @@ export default ({ getService }: FtrProviderContext): void => { label: 'toggle', type: CustomFieldTypes.TOGGLE, required: true, + defaultValue: false, }, ], owner: 'securitySolutionFixture', @@ -553,6 +562,7 @@ export default ({ getService }: FtrProviderContext): void => { label: 'toggle', type: CustomFieldTypes.TOGGLE, required: true, + defaultValue: false, }, ], }, @@ -583,6 +593,7 @@ export default ({ getService }: FtrProviderContext): void => { label: 'toggle', type: CustomFieldTypes.TOGGLE, required: true, + defaultValue: false, }, ], }, @@ -613,6 +624,7 @@ export default ({ getService }: FtrProviderContext): void => { label: 'toggle', type: CustomFieldTypes.TOGGLE, required: true, + defaultValue: false, }, ], }, 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 8ab9afd818146..3cf0810c7a03e 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 @@ -181,6 +181,10 @@ export function RulePagePageProvider({ getService, getPageObjects }: FtrProvider const disabledRulesButton = await testSubjects.find('rules-counters-disabled-rules-button'); await disabledRulesButton.click(); }, + + doesElementExist: async (selector: string) => { + return await testSubjects.exists(selector); + }, }; const navigateToRulePage = async (benchmarkCisId: string, benchmarkCisVersion: string) => { diff --git a/x-pack/test/cloud_security_posture_functional/pages/rules.ts b/x-pack/test/cloud_security_posture_functional/pages/rules.ts index 81629da46439c..b734c391bf27c 100644 --- a/x-pack/test/cloud_security_posture_functional/pages/rules.ts +++ b/x-pack/test/cloud_security_posture_functional/pages/rules.ts @@ -28,8 +28,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { 'findings', ]); - // Failing: See https://github.com/elastic/kibana/issues/175905 - describe.skip('Cloud Posture Rules Page', function () { + describe('Cloud Posture Rules Page', function () { this.tags(['cloud_security_posture_rules_page']); let rule: typeof pageObjects.rule; let findings: typeof pageObjects.findings; @@ -72,9 +71,81 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await findings.index.remove(); }); - // FLAKY: https://github.com/elastic/kibana/issues/175614 - describe.skip('Rules Page - Bulk Action buttons', () => { - it('It should disable both Enable and Disable options when there are no rules selected', async () => { + describe('Rules Page - Rules Counters', () => { + it('Shows posture score when there are findings', async () => { + const isEmptyStateVisible = await rule.rulePage.getCountersEmptyState(); + expect(isEmptyStateVisible).to.be(false); + + const postureScoreCounter = await rule.rulePage.getPostureScoreCounter(); + expect((await postureScoreCounter.getVisibleText()).includes('33%')).to.be(true); + }); + + it('Clicking the posture score button leads to the dashboard', async () => { + await rule.rulePage.clickPostureScoreButton(); + await pageObjects.common.waitUntilUrlIncludes('cloud_security_posture/dashboard'); + }); + + it('Shows integrations count when there are findings', async () => { + const integrationsCounter = await rule.rulePage.getIntegrationsEvaluatedCounter(); + expect((await integrationsCounter.getVisibleText()).includes('1')).to.be(true); + }); + + it('Clicking the integrations counter button leads to the integration page', async () => { + await rule.rulePage.clickIntegrationsEvaluatedButton(); + await pageObjects.common.waitUntilUrlIncludes('add-integration/kspm'); + }); + + it('Shows the failed findings counter when there are findings', async () => { + const failedFindingsCounter = await rule.rulePage.getFailedFindingsCounter(); + expect((await failedFindingsCounter.getVisibleText()).includes('2')).to.be(true); + }); + + it('Clicking the failed findings button leads to the findings page', async () => { + await rule.rulePage.clickFailedFindingsButton(); + await pageObjects.common.waitUntilUrlIncludes( + 'cloud_security_posture/findings/configurations' + ); + }); + + it('Shows the disabled rules count', async () => { + const disabledRulesCounter = await rule.rulePage.getDisabledRulesCounter(); + expect((await disabledRulesCounter.getVisibleText()).includes('0')).to.be(true); + + // disable rule 1.1.1 (k8s findings mock contains a findings from that rule) + await rule.rulePage.clickEnableRulesRowSwitchButton(0); + await pageObjects.header.waitUntilLoadingHasFinished(); + expect((await disabledRulesCounter.getVisibleText()).includes('1')).to.be(true); + + const postureScoreCounter = await rule.rulePage.getPostureScoreCounter(); + expect((await postureScoreCounter.getVisibleText()).includes('0%')).to.be(true); + + // enable rule back + await rule.rulePage.clickEnableRulesRowSwitchButton(0); + }); + + it('Clicking the disabled rules button shows enables the disabled filter', async () => { + await rule.rulePage.clickEnableRulesRowSwitchButton(0); + await pageObjects.header.waitUntilLoadingHasFinished(); + + await rule.rulePage.clickDisabledRulesButton(); + await pageObjects.header.waitUntilLoadingHasFinished(); + expect((await rule.rulePage.getEnableRulesRowSwitchButton()) === 1).to.be(true); + }); + + it('Shows empty state when there are no findings', async () => { + // Ensure there are no findings initially + await findings.index.remove(); + await rule.navigateToRulePage('cis_k8s', '1.0.1'); + + const isEmptyStateVisible = await rule.rulePage.getCountersEmptyState(); + expect(isEmptyStateVisible).to.be(true); + await rule.rulePage.clickEnableRulesRowSwitchButton(0); + }); + }); + + describe('Rules Page - Bulk Action buttons', () => { + it('It should disable Enable option when there are all rules selected are already enabled ', async () => { + await rule.rulePage.clickSelectAllRules(); await rule.rulePage.toggleBulkActionButton(); expect( (await rule.rulePage.isBulkActionOptionDisabled(RULES_BULK_ACTION_OPTION_ENABLE)) === @@ -83,11 +154,10 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { expect( (await rule.rulePage.isBulkActionOptionDisabled(RULES_BULK_ACTION_OPTION_DISABLE)) === 'true' - ).to.be(true); + ).to.be(false); }); - it('It should disable Enable option when there are all rules selected are already enabled ', async () => { - await rule.rulePage.clickSelectAllRules(); + it('It should disable both Enable and Disable options when there are no rules selected', async () => { await rule.rulePage.toggleBulkActionButton(); expect( (await rule.rulePage.isBulkActionOptionDisabled(RULES_BULK_ACTION_OPTION_ENABLE)) === @@ -96,7 +166,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { expect( (await rule.rulePage.isBulkActionOptionDisabled(RULES_BULK_ACTION_OPTION_DISABLE)) === 'true' - ).to.be(false); + ).to.be(true); }); it('It should disable Disable option when there are all rules selected are already Disabled', async () => { @@ -178,6 +248,15 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await pageObjects.header.waitUntilLoadingHasFinished(); expect((await rule.rulePage.getEnableSwitchButtonState()) === 'false').to.be(true); }); + it('Alerts section of Rules Flyout shows Disabled text when Rules are disabled', async () => { + await rule.rulePage.clickRulesNames(0); + await pageObjects.header.waitUntilLoadingHasFinished(); + expect( + (await rule.rulePage.doesElementExist( + 'csp:findings-flyout-create-detection-rule-link' + )) === false + ).to.be(true); + }); it('Users are able to Enable/Disable Rule from Take Action on Rule Flyout', async () => { await rule.rulePage.clickRulesNames(0); await rule.rulePage.clickTakeActionButton(); @@ -185,78 +264,14 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await pageObjects.header.waitUntilLoadingHasFinished(); expect((await rule.rulePage.getEnableSwitchButtonState()) === 'true').to.be(true); }); - }); - - describe('Rules Page - Rules Counters', () => { - it('Shows posture score when there are findings', async () => { - const isEmptyStateVisible = await rule.rulePage.getCountersEmptyState(); - expect(isEmptyStateVisible).to.be(false); - - const postureScoreCounter = await rule.rulePage.getPostureScoreCounter(); - expect((await postureScoreCounter.getVisibleText()).includes('33%')).to.be(true); - }); - - it('Clicking the posture score button leads to the dashboard', async () => { - await rule.rulePage.clickPostureScoreButton(); - await pageObjects.common.waitUntilUrlIncludes('cloud_security_posture/dashboard'); - }); - - it('Shows integrations count when there are findings', async () => { - const integrationsCounter = await rule.rulePage.getIntegrationsEvaluatedCounter(); - expect((await integrationsCounter.getVisibleText()).includes('1')).to.be(true); - }); - - it('Clicking the integrations counter button leads to the integration page', async () => { - await rule.rulePage.clickIntegrationsEvaluatedButton(); - await pageObjects.common.waitUntilUrlIncludes( - 'cloud_security_posture/add-integration/kspm' - ); - }); - - it('Shows the failed findings counter when there are findings', async () => { - const failedFindingsCounter = await rule.rulePage.getFailedFindingsCounter(); - expect((await failedFindingsCounter.getVisibleText()).includes('2')).to.be(true); - }); - - it('Clicking the failed findings button leads to the findings page', async () => { - await rule.rulePage.clickFailedFindingsButton(); - await pageObjects.common.waitUntilUrlIncludes( - 'cloud_security_posture/findings/configurations' - ); - }); - - it('Shows the disabled rules count', async () => { - const disabledRulesCounter = await rule.rulePage.getDisabledRulesCounter(); - expect((await disabledRulesCounter.getVisibleText()).includes('0')).to.be(true); - - // disable rule 1.1.1 (k8s findings mock contains a findings from that rule) - await rule.rulePage.clickEnableRulesRowSwitchButton(0); - await pageObjects.header.waitUntilLoadingHasFinished(); - expect((await disabledRulesCounter.getVisibleText()).includes('1')).to.be(true); - - const postureScoreCounter = await rule.rulePage.getPostureScoreCounter(); - expect((await postureScoreCounter.getVisibleText()).includes('0%')).to.be(true); - - // enable rule back - await rule.rulePage.clickEnableRulesRowSwitchButton(0); - }); - - it('Clicking the disabled rules button shows enables the disabled filter', async () => { - await rule.rulePage.clickEnableRulesRowSwitchButton(0); - await pageObjects.header.waitUntilLoadingHasFinished(); - - await rule.rulePage.clickDisabledRulesButton(); + it('Alerts section of Rules Flyout shows Detection Rule Counter component when Rules are enabled', async () => { + await rule.rulePage.clickRulesNames(0); await pageObjects.header.waitUntilLoadingHasFinished(); - expect((await rule.rulePage.getEnableRulesRowSwitchButton()) === 1).to.be(true); - }); - - it('Shows empty state when there are no findings', async () => { - // Ensure there are no findings initially - await findings.index.remove(); - await rule.navigateToRulePage('cis_k8s', '1.0.1'); - - const isEmptyStateVisible = await rule.rulePage.getCountersEmptyState(); - expect(isEmptyStateVisible).to.be(true); + expect( + (await rule.rulePage.doesElementExist( + 'csp:findings-flyout-create-detection-rule-link' + )) === true + ).to.be(true); }); }); }); diff --git a/x-pack/test/fleet_api_integration/apis/data_streams/list.ts b/x-pack/test/fleet_api_integration/apis/data_streams/list.ts index 905d492e29341..5b93affbb82ec 100644 --- a/x-pack/test/fleet_api_integration/apis/data_streams/list.ts +++ b/x-pack/test/fleet_api_integration/apis/data_streams/list.ts @@ -111,26 +111,48 @@ export default function (providerContext: FtrProviderContext) { beforeEach(async () => { await installPackage(pkgName, pkgVersion); + + // Create index template that do use final pipeline + const sourceIndexTemplate = ( + await es.indices.getIndexTemplate({ + name: logsTemplateName, + }) + ).index_templates[0].index_template; + await es.indices.putIndexTemplate({ + name: `${logsTemplateName}-testwithoutfinalpipeline`, + template: sourceIndexTemplate.template, + _meta: sourceIndexTemplate._meta, + data_stream: sourceIndexTemplate.data_stream, + composed_of: sourceIndexTemplate.composed_of.filter( + (template) => template.includes('@settings') || template.includes('@package') + ), + index_patterns: [`${logsTemplateName}-testwithoutfinalpipeline`], + priority: 500, + }); }); afterEach(async () => { - await uninstallPackage(pkgName, pkgVersion); - try { - await es.transport.request({ - method: 'DELETE', - path: `/_data_stream/${logsTemplateName}-default`, - }); - await es.transport.request({ - method: 'DELETE', - path: `/_data_stream/${metricsTemplateName}-default`, - }); - await es.transport.request({ - method: 'DELETE', - path: `/_data_stream/${notFleetTemplateName}-default`, - }); - } catch (e) { - // Silently swallow errors here as not all tests seed data streams + const pathsToDelete = [ + `/_data_stream/${logsTemplateName}-default`, + `/_data_stream/${metricsTemplateName}-default`, + `/_data_stream/${notFleetTemplateName}-default`, + `/_data_stream/${logsTemplateName}-testwithoutfinalpipeline`, + ]; + + for (const path of pathsToDelete) { + await es.transport + .request({ + method: 'DELETE', + path, + }) + // Silently swallow errors here as not all tests seed data streams + .catch((e) => {}); } + + await es.indices.deleteIndexTemplate({ + name: `${logsTemplateName}-testwithoutfinalpipeline`, + }); + await uninstallPackage(pkgName, pkgVersion); }); it("should return no data streams when there isn't any data yet", async function () { @@ -196,6 +218,40 @@ export default function (providerContext: FtrProviderContext) { }); }); + it('should work for datastream without event.ingested and use @timestamp for last_activity_ms', async function () { + const timestamp = Date.now() - 1000 * 60 * 60; + + await es.index({ + index: `${logsTemplateName}-testwithoutfinalpipeline`, + refresh: 'wait_for', + + document: { + '@timestamp': new Date(timestamp).toISOString(), + logs_test_name: 'test', + data_stream: { + dataset: `${pkgName}.test_logs`, + namespace: 'testwithoutfinalpipeline', + type: 'logs', + }, + }, + }); + + const res = await es.search({ + index: `${logsTemplateName}-testwithoutfinalpipeline`, + }); + + expect(res.hits.hits.length).to.eql(1); + expect((res.hits.hits[0]._source as any).event).to.eql(undefined); + + const { body } = await getDataStreams(); + expect(body.data_streams.length).to.eql(1); + + const dataStream = body.data_streams[0]; + + expect(dataStream.dataset).to.eql('datastreams.test_logs'); + expect(dataStream.last_activity_ms).to.eql(timestamp); + }); + it('should return correct number of data streams regardless of number of backing indices', async function () { await seedDataStreams(); await retry.tryForTime(10000, async () => { diff --git a/x-pack/test/fleet_api_integration/apis/epm/install_integration_in_multiple_spaces.ts b/x-pack/test/fleet_api_integration/apis/epm/install_integration_in_multiple_spaces.ts index ead132cdfc860..186c562cc0fcf 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/install_integration_in_multiple_spaces.ts +++ b/x-pack/test/fleet_api_integration/apis/epm/install_integration_in_multiple_spaces.ts @@ -4,7 +4,9 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { INGEST_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import expect from '@kbn/expect'; +import { PACKAGES_SAVED_OBJECT_TYPE } from '@kbn/fleet-plugin/common'; import pRetry from 'p-retry'; import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; import { skipIfNoDockerRegistry } from '../../helpers'; @@ -19,6 +21,8 @@ export default function (providerContext: FtrProviderContext) { const dockerServers = getService('dockerServers'); const esArchiver = getService('esArchiver'); const server = dockerServers.get('registry'); + const es = getService('es'); + const pkgName = 'system'; const pkgVersion = '1.27.0'; @@ -37,7 +41,7 @@ export default function (providerContext: FtrProviderContext) { }; const installPackageInSpace = async (pkg: string, version: string, spaceId: string) => { - await supertest + return supertest .post(`/s/${spaceId}/api/fleet/epm/packages/${pkg}/${version}`) .set('kbn-xsrf', 'xxxx') .send({ force: true }) @@ -68,7 +72,7 @@ export default function (providerContext: FtrProviderContext) { }) .catch(() => {}); - describe.skip('When installing system integration in multiple spaces', async () => { + describe('When installing system integration in multiple spaces', async () => { skipIfNoDockerRegistry(providerContext); setupFleetAndAgents(providerContext); @@ -130,5 +134,41 @@ export default function (providerContext: FtrProviderContext) { const spaceTag = await getTag(`fleet-pkg-${pkgName}-fleet_test_space`, testSpaceId); expect(spaceTag).not.equal(undefined); }); + + it('should keep assets in space when format version is bumped', async () => { + const nginxPkgName = 'nginx'; + const nginxPkgVersion = '1.17.0'; + + const installResponse = await installPackageInSpace( + nginxPkgName, + nginxPkgVersion, + testSpaceId + ); + + const firstAsset = installResponse.body.items.find((item: any) => item.type === 'dashboard'); + + // Bump format version directly on installation saved object, then call setup to trigger a reinstall + await es.update({ + index: INGEST_SAVED_OBJECT_INDEX, + id: `${PACKAGES_SAVED_OBJECT_TYPE}:${nginxPkgName}`, + body: { + doc: { + [PACKAGES_SAVED_OBJECT_TYPE]: { + install_format_schema_version: '99.99.99', + }, + }, + }, + }); + + await supertest.post(`/api/fleet/setup`).set('kbn-xsrf', 'xxxx').send(); + + const res = await es.get({ + index: '.kibana_analytics', + id: `${firstAsset.type}:${firstAsset.id}`, + }); + + expect(res.found).to.be(true); + expect((res._source as any).namespaces).to.eql([testSpaceId]); + }); }); } 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 30c61a58c0545..9799d4418d729 100644 --- a/x-pack/test/functional/apps/aiops/log_rate_analysis.ts +++ b/x-pack/test/functional/apps/aiops/log_rate_analysis.ts @@ -36,7 +36,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await ml.jobSourceSelection.selectSourceForLogRateAnalysis(testData.sourceIndexOrSavedSearch); }); - it(`${testData.suiteTitle} displays index details`, async () => { + // FLAKY: https://github.com/elastic/kibana/issues/176387 + it.skip(`${testData.suiteTitle} displays index details`, async () => { await ml.testExecution.logTestStep(`${testData.suiteTitle} displays the time range step`); await aiops.logRateAnalysisPage.assertTimeRangeSelectorSectionExists(); @@ -274,8 +275,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); } - // FLAKY: https://github.com/elastic/kibana/issues/176066 - describe.skip('log rate analysis', async function () { + describe('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/saved_searches.ts b/x-pack/test/functional/apps/discover/saved_searches.ts index c95249e927084..8f5fe5dc9bc11 100644 --- a/x-pack/test/functional/apps/discover/saved_searches.ts +++ b/x-pack/test/functional/apps/discover/saved_searches.ts @@ -45,8 +45,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.common.unsetTime(); }); - // FLAKY: https://github.com/elastic/kibana/issues/104578 - describe.skip('Customize time range', () => { + describe('Customize time range', () => { it('should be possible to customize time range for saved searches on dashboards', async () => { await PageObjects.dashboard.navigateToApp(); await PageObjects.dashboard.clickNewDashboard(); 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 a42d5b5a0c94c..545d7ad1c4dee 100644 --- a/x-pack/test/functional/apps/managed_content/managed_content.ts +++ b/x-pack/test/functional/apps/managed_content/managed_content.ts @@ -16,10 +16,12 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { 'common', 'discover', 'maps', + 'dashboard', ]); const kibanaServer = getService('kibanaServer'); const esArchiver = getService('esArchiver'); const testSubjects = getService('testSubjects'); + const dashboardAddPanel = getService('dashboardAddPanel'); describe('Managed Content', () => { before(async () => { @@ -32,19 +34,19 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { kibanaServer.importExport.unload('test/functional/fixtures/kbn_archiver/managed_content'); }); - const expectManagedContentSignifiers = async ( - expected: boolean, - saveButtonTestSubject: string - ) => { - await testSubjects[expected ? 'existOrFail' : 'missingOrFail']('managedContentBadge'); - await testSubjects.click(saveButtonTestSubject); - - const saveAsNewCheckbox = await testSubjects.find('saveAsNewCheckbox'); - expect(await testSubjects.isEuiSwitchChecked(saveAsNewCheckbox)).to.be(expected); - expect(await saveAsNewCheckbox.getAttribute('disabled')).to.be(expected ? 'true' : null); - }; - describe('preventing the user from overwriting managed content', () => { + const expectManagedContentSignifiers = async ( + expected: boolean, + saveButtonTestSubject: string + ) => { + await testSubjects[expected ? 'existOrFail' : 'missingOrFail']('managedContentBadge'); + await testSubjects.click(saveButtonTestSubject); + + const saveAsNewCheckbox = await testSubjects.find('saveAsNewCheckbox'); + expect(await testSubjects.isEuiSwitchChecked(saveAsNewCheckbox)).to.be(expected); + expect(await saveAsNewCheckbox.getAttribute('disabled')).to.be(expected ? 'true' : null); + }; + it('lens', async () => { await PageObjects.common.navigateToActualUrl( 'lens', @@ -64,60 +66,105 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await expectManagedContentSignifiers(false, 'lnsApp_saveButton'); }); - }); - it('discover', async () => { - await PageObjects.common.navigateToActualUrl( - 'discover', - 'view/managed-3d62-4113-ac7c-de2e20a68fbc' - ); - await PageObjects.discover.waitForDiscoverAppOnScreen(); + it('discover', async () => { + await PageObjects.common.navigateToActualUrl( + 'discover', + 'view/managed-3d62-4113-ac7c-de2e20a68fbc' + ); + await PageObjects.discover.waitForDiscoverAppOnScreen(); + + await expectManagedContentSignifiers(true, 'discoverSaveButton'); + + await PageObjects.common.navigateToActualUrl( + 'discover', + 'view/unmanaged-3d62-4113-ac7c-de2e20a68fbc' + ); + await PageObjects.discover.waitForDiscoverAppOnScreen(); + + await expectManagedContentSignifiers(false, 'discoverSaveButton'); + }); - await expectManagedContentSignifiers(true, 'discoverSaveButton'); + it('visualize', async () => { + await PageObjects.common.navigateToActualUrl( + 'visualize', + 'edit/managed-feb9-4ba6-9538-1b8f67fb4f57' + ); + await PageObjects.visChart.waitForVisualization(); - await PageObjects.common.navigateToActualUrl( - 'discover', - 'view/unmanaged-3d62-4113-ac7c-de2e20a68fbc' - ); - await PageObjects.discover.waitForDiscoverAppOnScreen(); + await expectManagedContentSignifiers(true, 'visualizeSaveButton'); - await expectManagedContentSignifiers(false, 'discoverSaveButton'); - }); + await PageObjects.common.navigateToActualUrl( + 'visualize', + 'edit/unmanaged-feb9-4ba6-9538-1b8f67fb4f57' + ); + await PageObjects.visChart.waitForVisualization(); + + await expectManagedContentSignifiers(false, 'visualizeSaveButton'); + }); - it('visualize', async () => { - await PageObjects.common.navigateToActualUrl( - 'visualize', - 'edit/managed-feb9-4ba6-9538-1b8f67fb4f57' - ); - await PageObjects.visChart.waitForVisualization(); + it('maps', async () => { + await PageObjects.common.navigateToActualUrl( + 'maps', + 'map/managed-d7ab-46eb-a807-8fed28ed8566' + ); + await PageObjects.maps.waitForLayerAddPanelClosed(); - await expectManagedContentSignifiers(true, 'visualizeSaveButton'); + await expectManagedContentSignifiers(true, 'mapSaveButton'); - await PageObjects.common.navigateToActualUrl( - 'visualize', - 'edit/unmanaged-feb9-4ba6-9538-1b8f67fb4f57' - ); - await PageObjects.visChart.waitForVisualization(); + await PageObjects.common.navigateToActualUrl( + 'maps', + 'map/unmanaged-d7ab-46eb-a807-8fed28ed8566' + ); + await PageObjects.maps.waitForLayerAddPanelClosed(); - await expectManagedContentSignifiers(false, 'visualizeSaveButton'); + await expectManagedContentSignifiers(false, 'mapSaveButton'); + }); }); - it('maps', async () => { - await PageObjects.common.navigateToActualUrl( - 'maps', - 'map/managed-d7ab-46eb-a807-8fed28ed8566' - ); - await PageObjects.maps.waitForLayerAddPanelClosed(); + describe('managed panels in dashboards', () => { + it('inlines panels when managed dashboard cloned', async () => { + await PageObjects.common.navigateToActualUrl( + 'dashboard', + 'view/c44c86f9-b105-4a9c-9a24-449a58a827f3' + ); + + await PageObjects.dashboard.waitForRenderComplete(); + + await PageObjects.dashboard.clickClone(); - await expectManagedContentSignifiers(true, 'mapSaveButton'); + await PageObjects.dashboard.waitForRenderComplete(); - await PageObjects.common.navigateToActualUrl( - 'maps', - 'map/unmanaged-d7ab-46eb-a807-8fed28ed8566' - ); - await PageObjects.maps.waitForLayerAddPanelClosed(); + await testSubjects.missingOrFail('embeddablePanelNotification-ACTION_LIBRARY_NOTIFICATION'); + }); + + it('adds managed panels by-value', async () => { + await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.gotoDashboardLandingPage(); + await PageObjects.dashboard.clickNewDashboard(); + + await dashboardAddPanel.addEmbeddables([ + { name: 'Managed lens vis', type: 'lens' }, + { name: 'Managed legacy visualization', type: 'visualization' }, + { name: 'Managed map', type: 'map' }, + { name: 'Managed saved search', type: 'search' }, + ]); + + await testSubjects.missingOrFail('embeddablePanelNotification-ACTION_LIBRARY_NOTIFICATION'); + + await dashboardAddPanel.addEmbeddables([ + { name: 'Unmanaged lens vis', type: 'lens' }, + { name: 'Unmanaged legacy visualization', type: 'visualization' }, + { name: 'Unmanaged map', type: 'map' }, + { name: 'Unmanaged saved search', type: 'search' }, + ]); + + const byRefSignifiers = await testSubjects.findAll( + 'embeddablePanelNotification-ACTION_LIBRARY_NOTIFICATION' + ); - await expectManagedContentSignifiers(false, 'mapSaveButton'); + expect(byRefSignifiers.length).to.be(4); + }); }); }); } diff --git a/x-pack/test/functional/apps/ml/anomaly_detection_result_views/anomaly_explorer.ts b/x-pack/test/functional/apps/ml/anomaly_detection_result_views/anomaly_explorer.ts index a6e867676d10f..76bed212eebde 100644 --- a/x-pack/test/functional/apps/ml/anomaly_detection_result_views/anomaly_explorer.ts +++ b/x-pack/test/functional/apps/ml/anomaly_detection_result_views/anomaly_explorer.ts @@ -543,6 +543,39 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ); }); }); + + describe('Use anomaly table action to view in Discover', function () { + beforeEach(async () => { + await ml.navigation.navigateToAnomalyExplorer( + testData.jobConfig.job_id, + { + from: '2016-02-07T00%3A00%3A00.000Z', + to: '2016-02-11T23%3A59%3A54.000Z', + }, + () => elasticChart.setNewChartUiDebugFlag(true) + ); + + await ml.commonUI.waitForMlLoadingIndicatorToDisappear(); + await ml.commonUI.waitForDatePickerIndicatorLoaded(); + await ml.swimLane.waitForSwimLanesToLoad(); + }); + + it('should render the anomaly table', async () => { + await ml.testExecution.logTestStep('displays the anomalies table'); + await ml.anomaliesTable.assertTableExists(); + + await ml.testExecution.logTestStep('anomalies table is not empty'); + await ml.anomaliesTable.assertTableNotEmpty(); + }); + + it('should click the Discover action in the anomaly table', async () => { + await ml.anomaliesTable.assertAnomalyActionsMenuButtonExists(0); + await ml.anomaliesTable.scrollRowIntoView(0); + await ml.anomaliesTable.assertAnomalyActionsMenuButtonEnabled(0, true); + await ml.anomaliesTable.assertAnomalyActionDiscoverButtonExists(0); + await ml.anomaliesTable.ensureAnomalyActionDiscoverButtonClicked(0); + }); + }); }); } }); diff --git a/x-pack/test/functional/apps/ml/anomaly_detection_result_views/single_metric_viewer.ts b/x-pack/test/functional/apps/ml/anomaly_detection_result_views/single_metric_viewer.ts index 1779589a5a0c1..c9ceb71459e4a 100644 --- a/x-pack/test/functional/apps/ml/anomaly_detection_result_views/single_metric_viewer.ts +++ b/x-pack/test/functional/apps/ml/anomaly_detection_result_views/single_metric_viewer.ts @@ -90,6 +90,13 @@ export default function ({ getService }: FtrProviderContext) { await ml.testExecution.logTestStep('anomalies table is not empty'); await ml.anomaliesTable.assertTableNotEmpty(); }); + + it('should click on an anomaly marker', async () => { + await ml.singleMetricViewer.assertAnomalyMarkerExist(); + await ml.singleMetricViewer.openAnomalyMarkerActionsPopover(); + await ml.anomaliesTable.assertAnomalyActionDiscoverButtonExists(0); + await ml.anomaliesTable.ensureAnomalyActionDiscoverButtonClicked(0); + }); }); describe('with entity fields', function () { @@ -193,7 +200,9 @@ export default function ({ getService }: FtrProviderContext) { // Also sorting by name is enforced because the model plot is enabled // and anomalous only is disabled await ml.singleMetricViewer.assertEntityConfig('day_of_week', false, 'name', 'desc'); + }); + it('should render the singe metric viewer chart and anomaly table', async () => { await ml.testExecution.logTestStep('displays the chart'); await ml.singleMetricViewer.assertChartExist(); @@ -203,6 +212,14 @@ export default function ({ getService }: FtrProviderContext) { await ml.testExecution.logTestStep('anomalies table is not empty'); await ml.anomaliesTable.assertTableNotEmpty(); }); + + it('should click the Discover action in the anomaly table', async () => { + await ml.anomaliesTable.assertAnomalyActionsMenuButtonExists(0); + await ml.anomaliesTable.scrollRowIntoView(0); + await ml.anomaliesTable.assertAnomalyActionsMenuButtonEnabled(0, true); + await ml.anomaliesTable.assertAnomalyActionDiscoverButtonExists(0); + await ml.anomaliesTable.ensureAnomalyActionDiscoverButtonClicked(0); + }); }); }); } diff --git a/x-pack/test/functional/services/ml/anomalies_table.ts b/x-pack/test/functional/services/ml/anomalies_table.ts index 52eaf5715f673..c59221289f848 100644 --- a/x-pack/test/functional/services/ml/anomalies_table.ts +++ b/x-pack/test/functional/services/ml/anomalies_table.ts @@ -131,6 +131,24 @@ export function MachineLearningAnomaliesTableProvider({ getService }: FtrProvide ); }, + async assertAnomalyActionDiscoverButtonExists(rowIndex: number) { + await this.ensureAnomalyActionsMenuOpen(rowIndex); + await testSubjects.existOrFail('mlAnomaliesListRowAction_viewInDiscoverButton'); + }, + + async assertAnomalyActionDiscoverButtonNotExists(rowIndex: number) { + await this.ensureAnomalyActionsMenuOpen(rowIndex); + await testSubjects.missingOrFail('mlAnomaliesListRowAction_viewInDiscoverButton'); + }, + + async ensureAnomalyActionDiscoverButtonClicked(rowIndex: number) { + await retry.tryForTime(10 * 1000, async () => { + await this.ensureAnomalyActionsMenuOpen(rowIndex); + await testSubjects.click('mlAnomaliesListRowAction_viewInDiscoverButton'); + await testSubjects.existOrFail('discoverLayoutResizableContainer'); + }); + }, + async assertAnomalyActionLogRateAnalysisButtonExists(rowIndex: number) { await this.ensureAnomalyActionsMenuOpen(rowIndex); await testSubjects.existOrFail('mlAnomaliesListRowAction_runLogRateAnalysisButton'); diff --git a/x-pack/test/functional/services/ml/single_metric_viewer.ts b/x-pack/test/functional/services/ml/single_metric_viewer.ts index 29f1ded74deba..05ae2bd20cab7 100644 --- a/x-pack/test/functional/services/ml/single_metric_viewer.ts +++ b/x-pack/test/functional/services/ml/single_metric_viewer.ts @@ -73,6 +73,15 @@ export function MachineLearningSingleMetricViewerProvider( await testSubjects.existOrFail('mlSingleMetricViewerChart'); }, + async assertAnomalyMarkerExist() { + await testSubjects.existOrFail('mlAnomalyMarker'); + }, + + async openAnomalyMarkerActionsPopover() { + await testSubjects.click('mlAnomalyMarker'); + await testSubjects.existOrFail('mlAnomaliesListRowActionsMenu'); + }, + async assertAnnotationsExists(state: string) { await testSubjects.existOrFail(`mlAnomalyExplorerAnnotations ${state}`, { timeout: 30 * 1000, diff --git a/x-pack/test/functional_with_es_ssl/apps/cases/group1/create_case_form.ts b/x-pack/test/functional_with_es_ssl/apps/cases/group1/create_case_form.ts index 93883f7fc9d22..fcb1e23d6f9bb 100644 --- a/x-pack/test/functional_with_es_ssl/apps/cases/group1/create_case_form.ts +++ b/x-pack/test/functional_with_es_ssl/apps/cases/group1/create_case_form.ts @@ -133,14 +133,14 @@ export default ({ getService, getPageObject }: FtrProviderContext) => { { key: 'valid_key_1', label: 'Summary', - type: CustomFieldTypes.TEXT, - required: true, + type: CustomFieldTypes.TEXT as const, + required: false, }, { key: 'valid_key_2', label: 'Sync', - type: CustomFieldTypes.TOGGLE, - required: true, + type: CustomFieldTypes.TOGGLE as const, + required: false, }, ]; @@ -182,6 +182,55 @@ export default ({ getService, getPageObject }: FtrProviderContext) => { ); expect(await sync.getAttribute('aria-checked')).equal('true'); }); + + it('creates a case with custom fields that have default values', async () => { + const customFields = [ + { + key: 'valid_key_3', + label: 'Summary required', + type: CustomFieldTypes.TEXT as const, + defaultValue: 'Default value', + required: true, + }, + { + key: 'valid_key_4', + label: 'Sync required', + type: CustomFieldTypes.TOGGLE as const, + defaultValue: true, + required: true, + }, + ]; + + await cases.api.createConfigWithCustomFields({ customFields, owner: 'cases' }); + + const caseTitle = 'test-' + uuidv4(); + await cases.create.openCreateCasePage(); + + // verify custom fields on create case page + await testSubjects.existOrFail('create-case-custom-fields'); + + await cases.create.setTitle(caseTitle); + await cases.create.setDescription('this is a test description'); + + // submit without touching the custom fields + await cases.create.submitCase(); + + await header.waitUntilLoadingHasFinished(); + + await testSubjects.existOrFail('case-view-title'); + + // validate custom fields + const textCustomField = await testSubjects.find( + `case-text-custom-field-${customFields[0].key}` + ); + + expect(await textCustomField.getVisibleText()).equal(customFields[0].defaultValue); + + const toggleCustomField = await testSubjects.find( + `case-toggle-custom-field-form-field-${customFields[1].key}` + ); + expect(await toggleCustomField.getAttribute('aria-checked')).equal('true'); + }); }); }); }; diff --git a/x-pack/test/functional_with_es_ssl/apps/cases/group1/view_case.ts b/x-pack/test/functional_with_es_ssl/apps/cases/group1/view_case.ts index cab3cc82e8a4b..01cd6174b01cc 100644 --- a/x-pack/test/functional_with_es_ssl/apps/cases/group1/view_case.ts +++ b/x-pack/test/functional_with_es_ssl/apps/cases/group1/view_case.ts @@ -1230,13 +1230,15 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { { key: 'valid_key_1', label: 'Summary', - type: CustomFieldTypes.TEXT, + type: CustomFieldTypes.TEXT as const, + defaultValue: 'foobar', required: true, }, { key: 'valid_key_2', label: 'Sync', - type: CustomFieldTypes.TOGGLE, + type: CustomFieldTypes.TOGGLE as const, + defaultValue: false, required: true, }, ]; diff --git a/x-pack/test/functional_with_es_ssl/apps/cases/group2/configure.ts b/x-pack/test/functional_with_es_ssl/apps/cases/group2/configure.ts index ce461d86b257d..c61077c4583c8 100644 --- a/x-pack/test/functional_with_es_ssl/apps/cases/group2/configure.ts +++ b/x-pack/test/functional_with_es_ssl/apps/cases/group2/configure.ts @@ -63,7 +63,7 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { await testSubjects.setValue('custom-field-label-input', 'Summary'); - await testSubjects.setCheckbox('text-custom-field-options-wrapper', 'check'); + await testSubjects.setCheckbox('text-custom-field-required-wrapper', 'check'); await testSubjects.click('custom-field-flyout-save'); expect(await testSubjects.exists('euiFlyoutCloseButton')).to.be(false); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/trial_license_complete_tier/alerts_compatibility.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/trial_license_complete_tier/alerts_compatibility.ts index 176dc29c8b1ca..25ef93cafaac0 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/trial_license_complete_tier/alerts_compatibility.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/trial_license_complete_tier/alerts_compatibility.ts @@ -592,7 +592,8 @@ export default ({ getService }: FtrProviderContext) => { }); }); - describe('EQL', () => { + // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/176270 + describe.skip('EQL', () => { beforeEach(async () => { await esArchiver.load('x-pack/test/functional/es_archives/security_solution/alerts/7.16.0'); await createAlertsIndex(supertest, log); @@ -635,7 +636,8 @@ export default ({ getService }: FtrProviderContext) => { }); }); - describe('Threshold', () => { + // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/176359 + describe.skip('Threshold', () => { beforeEach(async () => { await esArchiver.load('x-pack/test/functional/es_archives/security_solution/alerts/7.16.0'); await createAlertsIndex(supertest, log); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/workflows/trial_license_complete_tier/role_based_rule_exceptions_workflows.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/workflows/trial_license_complete_tier/role_based_rule_exceptions_workflows.ts index ef78015ef7715..e308732db3821 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/workflows/trial_license_complete_tier/role_based_rule_exceptions_workflows.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/workflows/trial_license_complete_tier/role_based_rule_exceptions_workflows.ts @@ -803,7 +803,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('generates no alerts when a value list exception is added for a query rule', async () => { - const valueListId = 'value-list-id'; + const valueListId = 'value-list-id.txt'; await importFile(supertest, log, 'keyword', ['suricata-sensor-amsterdam'], valueListId); const rule: QueryRuleCreateProps = { name: 'Simple Rule Query', @@ -835,7 +835,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('generates no alerts when a value list exception is added for a threat match rule', async () => { - const valueListId = 'value-list-id'; + const valueListId = 'value-list-id.txt'; await importFile(supertest, log, 'keyword', ['zeek-sensor-amsterdam'], valueListId); const rule: ThreatMatchRuleCreateProps = { description: 'Detecting root and admin users', @@ -883,7 +883,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('generates no alerts when a value list exception is added for a threshold rule', async () => { - const valueListId = 'value-list-id'; + const valueListId = 'value-list-id.txt'; await importFile(supertest, log, 'keyword', ['zeek-sensor-amsterdam'], valueListId); const rule: ThresholdRuleCreateProps = { description: 'Detecting root and admin users', @@ -920,7 +920,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('generates no alerts when a value list exception is added for an EQL rule', async () => { - const valueListId = 'value-list-id'; + const valueListId = 'value-list-id.txt'; await importFile(supertest, log, 'keyword', ['zeek-sensor-amsterdam'], valueListId); const rule: EqlRuleCreateProps = { ...getEqlRuleForAlertTesting(['auditbeat-*']), @@ -944,7 +944,7 @@ export default ({ getService }: FtrProviderContext) => { expect(alertsOpen.hits.hits.length).toEqual(0); }); it('should Not allow deleting value list when there are references and ignoreReferences is false', async () => { - const valueListId = 'value-list-id'; + const valueListId = 'value-list-id.txt'; await importFile(supertest, log, 'keyword', ['suricata-sensor-amsterdam'], valueListId); const rule: QueryRuleCreateProps = { ...getSimpleRule(), diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/workflows/trial_license_complete_tier/rule_exception_synchronizations.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/workflows/trial_license_complete_tier/rule_exception_synchronizations.ts index c584ec46f2ef1..5a68270e1220a 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/workflows/trial_license_complete_tier/rule_exception_synchronizations.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/workflows/trial_license_complete_tier/rule_exception_synchronizations.ts @@ -107,7 +107,7 @@ export default ({ getService }: FtrProviderContext) => { it('should Not allow editing an Exception with deleted ValueList', async () => { await createListsIndex(supertest, log); - const valueListId = 'value-list-id'; + const valueListId = 'value-list-id.txt'; await importFile(supertest, log, 'keyword', ['suricata-sensor-amsterdam'], valueListId); const rule: QueryRuleCreateProps = { ...getSimpleRule(), 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 e222f1ddd7cb4..60ad53f94937f 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 @@ -229,7 +229,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('generates no alerts when a value list exception is added for an ML rule', async () => { - const valueListId = 'value-list-id'; + const valueListId = 'value-list-id.txt'; await importFile(supertest, log, 'keyword', ['mothra'], valueListId); const { previewId } = await previewRuleWithExceptionEntries({ supertest, 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 99a96ae9052b1..7fabd749bc01d 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 @@ -54,6 +54,39 @@ export default ({ getService }: FtrProviderContext): void => { await deleteListsIndex(supertest, log); }); + it('should not import a list item if the imported file is not .txt or .csv', async () => { + const { body } = await supertest + .post(`${LIST_ITEM_URL}/_import?type=ip`) + .set('kbn-xsrf', 'true') + .attach('file', getImportListItemAsBuffer(['127.0.0.1', '127.0.0.2']), 'list_items.pdf') + .expect('Content-Type', 'application/json; charset=utf-8') + .expect(415); + + expect(body).to.eql({ + status_code: 415, + message: 'Unsupported media type. File must be one of the following types: [.csv, .txt]', + }); + }); + + it('should not import a list item if the imported file exceed the file size limit', async () => { + const { body } = await supertest + .post(`${LIST_ITEM_URL}/_import?type=ip`) + .set('kbn-xsrf', 'true') + .attach( + 'file', + getImportListItemAsBuffer(Array(1000000).fill('127.0.0.1')), + 'list_items.txt' + ) + .expect('Content-Type', 'application/json; charset=utf-8') + .expect(413); + + expect(body).to.eql({ + statusCode: 413, + error: 'Request Entity Too Large', + message: 'Payload content length greater than maximum allowed: 9000000', + }); + }); + it('should set the response content types to be expected when importing two items', async () => { await supertest .post(`${LIST_ITEM_URL}/_import?type=ip`) diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/detection_alerts/assignments/assignments.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/detection_alerts/assignments/assignments.cy.ts index 5dbf14796c583..21a67b7fb4ea4 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/detection_alerts/assignments/assignments.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/detection_alerts/assignments/assignments.cy.ts @@ -42,8 +42,7 @@ import { } from '../../../../../tasks/alert_assignments'; import { ALERTS_COUNT } from '../../../../../screens/alerts'; -// Failing: See https://github.com/elastic/kibana/issues/173429 -describe.skip('Alert user assignment - ESS & Serverless', { tags: ['@ess', '@serverless'] }, () => { +describe('Alert user assignment - ESS & Serverless', { tags: ['@ess', '@serverless'] }, () => { before(() => { cy.task('esArchiverLoad', { archiveName: 'auditbeat_multiple' }); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/detection_alerts/assignments/assignments_serverless_complete.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/detection_alerts/assignments/assignments_serverless_complete.cy.ts index 72afcb304f893..f026e4b2262e4 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/detection_alerts/assignments/assignments_serverless_complete.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/detection_alerts/assignments/assignments_serverless_complete.cy.ts @@ -20,8 +20,7 @@ import { loadPageAs, } from '../../../../../tasks/alert_assignments'; -// FLAKY: https://github.com/elastic/kibana/issues/172557 -describe.skip( +describe( 'Alert user assignment - Serverless Complete', { tags: ['@serverless'], diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/detection_alerts/assignments/assignments_serverless_essentials.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/detection_alerts/assignments/assignments_serverless_essentials.cy.ts index 5ae60a01a0e8b..f8bbf2620a542 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/detection_alerts/assignments/assignments_serverless_essentials.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/detection_alerts/assignments/assignments_serverless_essentials.cy.ts @@ -20,8 +20,7 @@ import { loadPageAs, } from '../../../../../tasks/alert_assignments'; -// FLAKY: https://github.com/elastic/kibana/issues/172520 -describe.skip( +describe( 'Alert user assignment - Serverless Essentials', { tags: ['@serverless'], diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timeline_templates/creation.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timeline_templates/creation.cy.ts index 2115238da2511..2c640bf3d4492 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timeline_templates/creation.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timeline_templates/creation.cy.ts @@ -6,31 +6,27 @@ */ import { getTimeline } from '../../../objects/timeline'; - import { LOCKED_ICON, PIN_EVENT, - TIMELINE_FLYOUT_WRAPPER, TIMELINE_QUERY, TIMELINE_TITLE, TIMELINE_DATE_PICKER_CONTAINER, + TIMELINE_TITLE_BY_ID, } from '../../../screens/timeline'; -import { TIMELINES_DESCRIPTION, TIMELINES_FAVORITE } from '../../../screens/timelines'; +import { TIMELINES_DESCRIPTION, TIMELINES_USERNAME } from '../../../screens/timelines'; import { createTimeline } from '../../../tasks/api_calls/timelines'; import { deleteTimelines } from '../../../tasks/api_calls/common'; import { login } from '../../../tasks/login'; import { visit } from '../../../tasks/navigation'; -import { openTimelineUsingToggle } from '../../../tasks/security_main'; import { addFilter, addNameToTimelineAndSave, clickingOnCreateTemplateFromTimelineBtn, closeTimeline, - createNewTimelineTemplate, createTimelineTemplateFromBottomBar, expandEventAction, - markAsFavorite, openTimelineTemplate, populateTimeline, addNameAndDescriptionToTimeline, @@ -43,35 +39,36 @@ import { setEndDateNow, } from '../../../tasks/date_picker'; import { waitForTimelinesPanelToBeLoaded } from '../../../tasks/timelines'; - import { TIMELINES_URL } from '../../../urls/navigation'; +import { + GET_LOCAL_DATE_PICKER_END_DATE_POPOVER_BUTTON, + GET_LOCAL_DATE_PICKER_START_DATE_POPOVER_BUTTON, +} from '../../../screens/date_picker'; +import { GLOBAL_SEARCH_BAR_FILTER_ITEM_AT } from '../../../screens/search_bar'; -// FLAKY: https://github.com/elastic/kibana/issues/175955 -describe.skip('Timeline Templates', { tags: ['@ess', '@serverless'] }, () => { +const mockTimeline = getTimeline(); + +describe('Timeline Templates', { tags: ['@ess', '@serverless'] }, () => { beforeEach(() => { login(); deleteTimelines(); cy.intercept('PATCH', '/api/timeline').as('timeline'); }); - it('Creates a timeline template', () => { + it('should create a timeline template from empty', () => { visit(TIMELINES_URL); - waitForTimelinesPanelToBeLoaded(); - openTimelineUsingToggle(); + createTimelineTemplateFromBottomBar(); - createNewTimelineTemplate(); populateTimeline(); - - cy.log('Add filter'); - addFilter(getTimeline().filter); - - cy.log('Update date range'); + addFilter(mockTimeline.filter); showStartEndDate(TIMELINE_DATE_PICKER_CONTAINER); setEndDateNow(TIMELINE_DATE_PICKER_CONTAINER); - setStartDate('Jan 18, 2018 @ 00:00:00.000', TIMELINE_DATE_PICKER_CONTAINER); + const startDate = 'Jan 18, 2018 @ 00:00:00.000'; + setStartDate(startDate, TIMELINE_DATE_PICKER_CONTAINER); updateTimelineDates(); cy.log('Try to pin an event'); + cy.get(PIN_EVENT).should( 'have.attr', 'aria-label', @@ -79,43 +76,66 @@ describe.skip('Timeline Templates', { tags: ['@ess', '@serverless'] }, () => { ); cy.get(LOCKED_ICON).should('be.visible'); - cy.log('Update title and description'); - addNameAndDescriptionToTimeline(getTimeline()); + cy.log('Save and close'); + + addNameAndDescriptionToTimeline(mockTimeline); + closeTimeline(); cy.wait('@timeline').then(({ response }) => { - const timelineId = response?.body.data.persistTimeline.timeline.savedObjectId; + const { createdBy, savedObjectId } = response?.body.data.persistTimeline.timeline; - markAsFavorite(); - closeTimeline(); + cy.log('Verify template shows on the table in the templates tab'); - cy.log('Open template from templates tab'); openTimelineTemplatesTab(); - openTimelineTemplate(timelineId); - - cy.log('Check that the template has been created correclty'); - cy.contains(getTimeline().title).should('exist'); - cy.get(TIMELINE_TITLE).should('have.text', getTimeline().title); - cy.get(TIMELINES_DESCRIPTION).first().should('have.text', getTimeline().description); - cy.get(TIMELINES_FAVORITE).first().should('exist'); - cy.get(TIMELINE_QUERY).should('contain.text', getTimeline().query); + + cy.get(TIMELINE_TITLE_BY_ID(savedObjectId)).should('have.text', mockTimeline.title); + cy.get(TIMELINES_DESCRIPTION).first().should('have.text', mockTimeline.description); + cy.get(TIMELINES_USERNAME).first().should('have.text', createdBy); + + cy.log('Open template and check that the template has been created correctly'); + + openTimelineTemplate(savedObjectId); + + cy.get(TIMELINE_TITLE).should('have.text', mockTimeline.title); + cy.get(TIMELINE_QUERY).should('contain.text', mockTimeline.query); + cy.get(GLOBAL_SEARCH_BAR_FILTER_ITEM_AT(0)) + .should('contain.text', mockTimeline.filter.field) + .and('contain.text', mockTimeline.filter.value); + cy.get( + GET_LOCAL_DATE_PICKER_START_DATE_POPOVER_BUTTON(TIMELINE_DATE_PICKER_CONTAINER) + ).should('have.text', startDate); + cy.get(GET_LOCAL_DATE_PICKER_END_DATE_POPOVER_BUTTON(TIMELINE_DATE_PICKER_CONTAINER)).should( + 'not.have.text', + 'now' + ); }); }); - it('Create template from timeline', () => { - createTimeline(getTimeline()); + it('should create a template from an existing timeline', () => { + createTimeline(mockTimeline); visit(TIMELINES_URL); waitForTimelinesPanelToBeLoaded(); expandEventAction(); clickingOnCreateTemplateFromTimelineBtn(); - addNameToTimelineAndSave('Test'); - cy.wait('@timeline', { timeout: 100000 }); - cy.get(TIMELINE_FLYOUT_WRAPPER).should('have.css', 'visibility', 'visible'); - cy.get(TIMELINE_QUERY).should('have.text', getTimeline().query); - }); + const savedName = 'Test'; + addNameToTimelineAndSave(savedName); - it('should create timeline template from bottombar', () => { - visit(TIMELINES_URL); - createTimelineTemplateFromBottomBar(); - cy.get(TIMELINE_TITLE).should('have.text', 'Untitled template'); + cy.wait('@timeline').then(({ response }) => { + const { createdBy, savedObjectId } = response?.body.data.persistTimeline.timeline; + + cy.log('Check that the template has been created correctly'); + + cy.get(TIMELINE_TITLE).should('have.text', savedName); + cy.get(TIMELINE_QUERY).should('have.text', mockTimeline.query); + + cy.log('Close timeline and verify template shows on the table in the templates tab'); + + closeTimeline(); + openTimelineTemplatesTab(); + + cy.get(TIMELINE_TITLE_BY_ID(savedObjectId)).should('have.text', savedName); + cy.get(TIMELINES_DESCRIPTION).first().should('have.text', mockTimeline.description); + cy.get(TIMELINES_USERNAME).first().should('have.text', createdBy); + }); }); }); diff --git a/x-pack/test/security_solution_cypress/cypress/objects/timeline.ts b/x-pack/test/security_solution_cypress/cypress/objects/timeline.ts index 88a3b04c410d8..a5e732c86b65f 100644 --- a/x-pack/test/security_solution_cypress/cypress/objects/timeline.ts +++ b/x-pack/test/security_solution_cypress/cypress/objects/timeline.ts @@ -52,12 +52,6 @@ export const getFavoritedTimeline = (): CompleteTimeline => ({ filter: getFilter(), }); -export const getIndicatorMatchTimelineTemplate = (): CompleteTimeline => ({ - ...getTimeline(), - title: 'Generic Threat Match Timeline', - templateTimelineId: '495ad7a7-316e-4544-8a0f-9c098daee76e', -}); - export const getTimelineModifiedSourcerer = () => ({ ...getTimeline(), title: 'Auditbeat Timeline', diff --git a/x-pack/test/security_solution_cypress/cypress/screens/date_picker.ts b/x-pack/test/security_solution_cypress/cypress/screens/date_picker.ts index 1433a60c26a88..09dd6a3575f37 100644 --- a/x-pack/test/security_solution_cypress/cypress/screens/date_picker.ts +++ b/x-pack/test/security_solution_cypress/cypress/screens/date_picker.ts @@ -33,8 +33,6 @@ export const GET_DATE_PICKER_END_DATE_POPOVER_BUTTON = ( container: string = GLOBAL_FILTERS_CONTAINER ) => `${container} [data-test-subj="superDatePickerendDatePopoverButton"]`; -export const DATE_PICKER_CONTAINER = `${GLOBAL_FILTERS_CONTAINER} .euiSuperDatePicker`; - export const LOCAL_DATE_PICKER_START_DATE_POPOVER_BUTTON = 'button[data-test-subj="superDatePickerstartDatePopoverButton"]'; @@ -45,7 +43,5 @@ export const SHOW_DATES_BUTTON = `${GLOBAL_FILTERS_CONTAINER} [data-test-subj="s export const GET_LOCAL_SHOW_DATES_BUTTON = (container: string) => `${container} [data-test-subj="superDatePickerShowDatesButton"]`; -export const DATE_PICKER_SHOW_DATE_POPOVER_BUTTON = `${GLOBAL_FILTERS_CONTAINER} ${SHOW_DATES_BUTTON}`; - export const GET_LOCAL_DATE_PICKER_START_DATE_POPOVER_BUTTON = (container: string = '') => `${container} [data-test-subj="superDatePickerstartDatePopoverButton"]`; diff --git a/x-pack/test/security_solution_cypress/cypress/screens/search_bar.ts b/x-pack/test/security_solution_cypress/cypress/screens/search_bar.ts index 3e700b8207939..766f7da5f131f 100644 --- a/x-pack/test/security_solution_cypress/cypress/screens/search_bar.ts +++ b/x-pack/test/security_solution_cypress/cypress/screens/search_bar.ts @@ -20,9 +20,6 @@ export const GET_LOCAL_SEARCH_BAR_SUBMIT_BUTTON = (localSearchBarSelector: strin export const ADD_FILTER_FORM_FIELD_INPUT = '[data-test-subj="filterFieldSuggestionList"] input[data-test-subj="comboBoxSearchInput"]'; -export const ADD_FILTER_FORM_FIELD_OPTION = (value: string) => - `[data-test-subj="comboBoxOptionsList filterFieldSuggestionList-optionsList"] button[title="${value}"]`; - export const ADD_FILTER_FORM_OPERATOR_FIELD = '[data-test-subj="filterOperatorList"] input[data-test-subj="comboBoxSearchInput"]'; diff --git a/x-pack/test/security_solution_cypress/cypress/screens/timeline.ts b/x-pack/test/security_solution_cypress/cypress/screens/timeline.ts index 964cf26ec13e2..6b06309158f7b 100644 --- a/x-pack/test/security_solution_cypress/cypress/screens/timeline.ts +++ b/x-pack/test/security_solution_cypress/cypress/screens/timeline.ts @@ -34,8 +34,6 @@ export const CREATE_NEW_TIMELINE = '[data-test-subj="timeline-modal-new-timeline export const BOTTOM_BAR_CREATE_NEW_TIMELINE = '[data-test-subj="timeline-bottom-bar-create-new-timeline"]'; -export const CREATE_NEW_TIMELINE_TEMPLATE = - '[data-test-subj="timeline-modal-new-timeline-template"]'; export const BOTTOM_BAR_CREATE_NEW_TIMELINE_TEMPLATE = '[data-test-subj="timeline-bottom-bar-create-new-timeline-template"]'; @@ -129,8 +127,6 @@ export const TIMELINE_ADD_FIELD_BUTTON = '[data-test-subj="addField"]'; export const TIMELINE_DATA_PROVIDER_FIELD = '[data-test-subj="field"]'; -export const TIMELINE_DATA_PROVIDER_FIELD_INPUT = '[data-test-subj="comboBoxSearchInput"]'; - export const TIMELINE_DATA_PROVIDER_OPERATOR = `[data-test-subj="operator"]`; export const TIMELINE_DATA_PROVIDER_VALUE = `[data-test-subj="value"]`; @@ -255,9 +251,6 @@ export const TIMELINE_TABS = '[data-test-subj="timeline"] .euiTabs'; export const TIMELINE_TAB_CONTENT_EQL = '[data-test-subj="timeline-tab-content-eql"]'; -export const TIMELINE_TAB_CONTENT_GRAPHS_NOTES = - '[data-test-subj="timeline-tab-content-graph-notes"]'; - export const TIMESTAMP_HOVER_ACTION_OVERFLOW_BTN = '[data-test-subj="event-fields-table-row-@timestamp"] [data-test-subj="showExtraActionsButton"]'; diff --git a/x-pack/test/security_solution_cypress/cypress/screens/timelines.ts b/x-pack/test/security_solution_cypress/cypress/screens/timelines.ts index 92343b07f3b99..8727c50e9272c 100644 --- a/x-pack/test/security_solution_cypress/cypress/screens/timelines.ts +++ b/x-pack/test/security_solution_cypress/cypress/screens/timelines.ts @@ -7,18 +7,8 @@ export const BULK_ACTIONS = '[data-test-subj="utility-bar-action-button"]'; -export const EXPAND_NOTES_BTN = '[data-test-subj="expand-notes"]'; - export const EXPORT_TIMELINE_ACTION = '[data-test-subj="export-timeline-action"]'; -export const IMPORT_BTN = '.euiButton.euiButton--primary.euiButton--fill'; - -export const IMPORT_TIMELINE_BTN = '[data-test-subj="open-import-data-modal-btn"]'; - -export const INPUT_FILE = 'input[type=file]'; - -export const NOTE = '[data-test-subj^="note-preview-"]'; - export const TIMELINE = (id: string | undefined) => { if (id == null) { throw new TypeError('id should never be null or undefined'); diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/common.ts b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/common.ts index 775b4c5a8964b..6c18c64d8d31c 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/common.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/common.ts @@ -134,15 +134,6 @@ export const deleteTimelines = () => { }); }; -export const deleteAlertsIndex = () => { - rootRequest({ - method: 'POST', - url: '/api/index_management/indices/delete', - body: { indices: ['.internal.alerts-security.alerts-default-000001'] }, - failOnStatusCode: false, - }); -}; - export const deleteAllCasesItems = () => { const kibanaIndexUrl = `${Cypress.env('ELASTICSEARCH_URL')}/.kibana_alerting_cases_\*`; rootRequest({ diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/date_picker.ts b/x-pack/test/security_solution_cypress/cypress/tasks/date_picker.ts index 99823f357dc90..d48483d4fe70c 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/date_picker.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/date_picker.ts @@ -22,9 +22,7 @@ import { export const setEndDateNow = (container: string = GLOBAL_FILTERS_CONTAINER) => { cy.get(GET_DATE_PICKER_END_DATE_POPOVER_BUTTON(container)).click(); - cy.get(DATE_PICKER_NOW_TAB).first().click(); - cy.get(DATE_PICKER_NOW_BUTTON).click(); }; @@ -62,7 +60,7 @@ export const updateDates = (container: string = GLOBAL_FILTERS_CONTAINER) => { }; export const updateTimelineDates = () => { - cy.get(DATE_PICKER_APPLY_BUTTON_TIMELINE).first().click({ force: true }); + cy.get(DATE_PICKER_APPLY_BUTTON_TIMELINE).first().click(); cy.get(DATE_PICKER_APPLY_BUTTON_TIMELINE).first().should('not.have.text', 'Updating'); }; @@ -94,8 +92,6 @@ export const updateDateRangeInLocalDatePickers = ( }; export const showStartEndDate = (container: string = GLOBAL_FILTERS_CONTAINER) => { - cy.get(GET_LOCAL_SHOW_DATES_BUTTON(container)).trigger('click'); - cy.get(GET_LOCAL_DATE_PICKER_START_DATE_POPOVER_BUTTON(container)).should('be.visible'); - // close date Popover - cy.get(GET_LOCAL_DATE_PICKER_START_DATE_POPOVER_BUTTON(container)).trigger('click'); + cy.get(GET_LOCAL_SHOW_DATES_BUTTON(container)).click(); + cy.get(GET_LOCAL_DATE_PICKER_START_DATE_POPOVER_BUTTON(container)).click(); }; diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/timeline.ts b/x-pack/test/security_solution_cypress/cypress/tasks/timeline.ts index ccc979d280c1a..dc5e1a048ac8b 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/timeline.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/timeline.ts @@ -45,7 +45,6 @@ import { TIMELINE_TITLE_BY_ID, TIMESTAMP_TOGGLE_FIELD, TOGGLE_TIMELINE_EXPAND_EVENT, - CREATE_NEW_TIMELINE_TEMPLATE, TIMELINE_SAVE_MODAL, TIMELINE_SAVE_MODAL_SAVE_BUTTON, TIMELINE_SAVE_MODAL_SAVE_AS_NEW_SWITCH, @@ -96,19 +95,6 @@ import { closeFieldsBrowser, filterFieldsBrowser } from './fields_browser'; const hostExistsQuery = 'host.name: *'; -export const addDescriptionToTimeline = ( - description: string, - modalAlreadyOpen: boolean = false -) => { - if (!modalAlreadyOpen) { - cy.get(SAVE_TIMELINE_ACTION_BTN).first().click(); - } - cy.get(TIMELINE_DESCRIPTION_INPUT).should('not.be.disabled').type(description); - cy.get(TIMELINE_DESCRIPTION_INPUT).invoke('val').should('equal', description); - cy.get(TIMELINE_SAVE_MODAL_SAVE_BUTTON).click(); - cy.get(TIMELINE_TITLE_INPUT).should('not.exist'); -}; - export const addNameToTimelineAndSave = (name: string) => { cy.get(SAVE_TIMELINE_ACTION_BTN).first().click(); cy.get(TIMELINE_TITLE_INPUT).should('not.be.disabled').clear(); @@ -348,11 +334,6 @@ export const createTimelineTemplateFromBottomBar = () => { cy.get(BOTTOM_BAR_CREATE_NEW_TIMELINE_TEMPLATE).eq(0).click(); }; -export const createNewTimelineTemplate = () => { - openCreateTimelineOptionsPopover(); - cy.get(CREATE_NEW_TIMELINE_TEMPLATE).click(); -}; - export const executeTimelineKQL = (query: string) => { cy.get(`${SEARCH_OR_FILTER_CONTAINER} textarea`).clear(); cy.get(`${SEARCH_OR_FILTER_CONTAINER} textarea`).type(`${query} {enter}`); diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/timelines.ts b/x-pack/test/security_solution_cypress/cypress/tasks/timelines.ts index 019971104973f..2b90fb3cd8abc 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/timelines.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/timelines.ts @@ -9,34 +9,13 @@ import { BULK_ACTIONS, EXPORT_TIMELINE, TIMELINE_CHECKBOX, - EXPAND_NOTES_BTN, EXPORT_TIMELINE_ACTION, - IMPORT_BTN, - IMPORT_TIMELINE_BTN, - INPUT_FILE, TIMELINES_TABLE, TIMELINE, TIMELINE_NAME, TIMELINE_ITEM_ACTION_BTN, } from '../screens/timelines'; import { SELECT_ALL_CHECKBOX } from '../screens/shared'; -import { - TIMELINE_COLLAPSED_ITEMS_BTN, - TIMELINE_CREATE_TIMELINE_FROM_TEMPLATE_BTN, -} from '../screens/timeline'; - -export const expandNotes = () => { - cy.get(EXPAND_NOTES_BTN).click(); -}; - -export const importTimeline = (timeline: string) => { - cy.get(IMPORT_TIMELINE_BTN).click(); - cy.get(INPUT_FILE).click(); - cy.get(INPUT_FILE).attachFile(timeline); - cy.get(INPUT_FILE).trigger('change'); - cy.get(IMPORT_BTN).last().click(); - cy.get(INPUT_FILE).should('not.exist'); -}; export const openTimeline = (id?: string) => { cy.get(id ? TIMELINE(id) : TIMELINE_NAME).click(); @@ -64,8 +43,3 @@ export const exportSelectedTimelines = () => { cy.get(EXPORT_TIMELINE_ACTION).should('not.be.disabled'); cy.get(EXPORT_TIMELINE_ACTION).click(); }; - -export const createTimelineFromFirstTemplateInList = () => { - cy.get(TIMELINE_COLLAPSED_ITEMS_BTN).first().click(); - cy.get(TIMELINE_CREATE_TIMELINE_FROM_TEMPLATE_BTN).click(); -}; diff --git a/x-pack/test/security_solution_cypress/cypress/urls/navigation.ts b/x-pack/test/security_solution_cypress/cypress/urls/navigation.ts index 0c8eb4806b325..7acaf9b277954 100644 --- a/x-pack/test/security_solution_cypress/cypress/urls/navigation.ts +++ b/x-pack/test/security_solution_cypress/cypress/urls/navigation.ts @@ -6,7 +6,6 @@ */ export const KIBANA_HOME = '/app/home#/'; -export const KIBANA_SAVED_OBJECTS = '/app/management/kibana/objects'; export const LOGOUT_URL = '/logout'; // Common diff --git a/x-pack/test_serverless/api_integration/test_suites/common/index_management/index_component_templates.ts b/x-pack/test_serverless/api_integration/test_suites/common/index_management/index_component_templates.ts index 9781a34fe0803..dba49769ed78a 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/index_management/index_component_templates.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/index_management/index_component_templates.ts @@ -107,6 +107,7 @@ export default function ({ getService }: FtrProviderContext) { usedBy: [], isManaged: false, hasSettings: true, + isDeprecated: false, hasMappings: true, hasAliases: false, }); @@ -118,6 +119,7 @@ export default function ({ getService }: FtrProviderContext) { const { body } = await getOneComponentTemplate(COMPONENT_NAME).expect(200); expect(body).to.eql({ + isDeprecated: false, name: COMPONENT_NAME, ...COMPONENT, _kbnMeta: { diff --git a/x-pack/test_serverless/api_integration/test_suites/common/search_xpack/search.ts b/x-pack/test_serverless/api_integration/test_suites/common/search_xpack/search.ts index ecced2db57ee9..477d3a518d164 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/search_xpack/search.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/search_xpack/search.ts @@ -222,28 +222,6 @@ export default function ({ getService }: FtrProviderContext) { verifyErrorResponse(resp.body, 400, 'Request must contain a kbn-xsrf header.'); }); - it('should return 400 when unknown index type is provided', async () => { - const resp = await supertest - .post(`/internal/search/ese`) - .set(ELASTIC_HTTP_VERSION_HEADER, '1') - // TODO: API requests in Serverless require internal request headers - .set(svlCommonApi.getInternalRequestHeader()) - .set('kbn-xsrf', 'foo') - .send({ - indexType: 'baad', - params: { - body: { - query: { - match_all: {}, - }, - }, - }, - }) - .expect(400); - - verifyErrorResponse(resp.body, 400, 'Unknown indexType'); - }); - it('should return 400 if invalid id is provided', async () => { const resp = await supertest .post(`/internal/search/ese/123`) diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/group_by_fired.ts b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/group_by_fired.ts index a80be6721f6af..ccd2aa6edaeaa 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/group_by_fired.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/group_by_fired.ts @@ -255,7 +255,7 @@ export default function ({ getService }: FtrProviderContext) { `${protocol}://${hostname}:${port}/app/observability/alerts?_a=(kuery:%27kibana.alert.uuid:%20%22${alertId}%22%27%2CrangeFrom:%27${rangeFrom}%27%2CrangeTo:now%2Cstatus:all)` ); expect(resp.hits.hits[0]._source?.reason).eql( - `Average system.cpu.total.norm.pct is 80%, above the threshold of 20%. (duration: 1 min, data view: ${DATA_VIEW}, group: host-0)` + `Average system.cpu.total.norm.pct is 80%, above or equal the threshold of 20%. (duration: 1 min, data view: ${DATA_VIEW}, group: host-0)` ); expect(resp.hits.hits[0]._source?.value).eql('80%'); expect(resp.hits.hits[0]._source?.host).eql( diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/p99_bytes_fired.ts b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/p99_bytes_fired.ts new file mode 100644 index 0000000000000..70f3192c117e9 --- /dev/null +++ b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/p99_bytes_fired.ts @@ -0,0 +1,199 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { cleanup, generate } from '@kbn/infra-forge'; +import { + Aggregators, + Comparator, +} from '@kbn/observability-plugin/common/custom_threshold_rule/types'; +import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/custom_threshold/constants'; +import expect from '@kbn/expect'; +import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/rule-data-utils'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const esClient = getService('es'); + const supertest = getService('supertest'); + const esDeleteAllIndices = getService('esDeleteAllIndices'); + const alertingApi = getService('alertingApi'); + const dataViewApi = getService('dataViewApi'); + const logger = getService('log'); + + describe('Custom Threshold rule - P99 - BYTES - FIRED', () => { + const CUSTOM_THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; + // DATE_VIEW should match the index template: + // x-pack/packages/kbn-infra-forge/src/data_sources/composable/template.json + const DATE_VIEW = 'kbn-data-forge-fake_hosts'; + const ALERT_ACTION_INDEX = 'alert-action-threshold'; + const DATA_VIEW_ID = 'data-view-id'; + let infraDataIndex: string; + let actionId: string; + let ruleId: string; + + before(async () => { + infraDataIndex = await generate({ + esClient, + lookback: 'now-15m', + logger, + }); + await dataViewApi.create({ + name: DATE_VIEW, + id: DATA_VIEW_ID, + title: DATE_VIEW, + }); + }); + + after(async () => { + await supertest + .delete(`/api/alerting/rule/${ruleId}`) + .set('kbn-xsrf', 'foo') + .set('x-elastic-internal-origin', 'foo'); + await supertest + .delete(`/api/actions/connector/${actionId}`) + .set('kbn-xsrf', 'foo') + .set('x-elastic-internal-origin', 'foo'); + await esClient.deleteByQuery({ + index: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, + query: { term: { 'kibana.alert.rule.uuid': ruleId } }, + conflicts: 'proceed', + }); + await esClient.deleteByQuery({ + index: '.kibana-event-log-*', + query: { term: { 'rule.id': ruleId } }, + conflicts: 'proceed', + }); + await dataViewApi.delete({ + id: DATA_VIEW_ID, + }); + await esDeleteAllIndices([ALERT_ACTION_INDEX, infraDataIndex]); + await cleanup({ esClient, logger }); + }); + + describe('Rule creation', () => { + it('creates rule successfully', async () => { + actionId = await alertingApi.createIndexConnector({ + name: 'Index Connector: Threshold API test', + indexName: ALERT_ACTION_INDEX, + }); + + const createdRule = await alertingApi.createRule({ + tags: ['observability'], + consumer: 'observability', + name: 'Threshold rule', + ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, + params: { + criteria: [ + { + comparator: Comparator.GT, + threshold: [1], + timeSize: 5, + timeUnit: 'm', + metrics: [ + { name: 'A', field: 'system.network.in.bytes', aggType: Aggregators.P99 }, + ], + }, + ], + alertOnNoData: true, + alertOnGroupDisappear: true, + searchConfiguration: { + query: { + query: '', + language: 'kuery', + }, + index: DATA_VIEW_ID, + }, + }, + actions: [ + { + group: FIRED_ACTIONS_ID, + id: actionId, + params: { + documents: [ + { + ruleType: '{{rule.type}}', + }, + ], + }, + frequency: { + notify_when: 'onActionGroupChange', + throttle: null, + summary: false, + }, + }, + ], + }); + ruleId = createdRule.id; + expect(ruleId).not.to.be(undefined); + }); + + it('should be active', async () => { + const executionStatus = await alertingApi.waitForRuleStatus({ + ruleId, + expectedStatus: 'active', + }); + expect(executionStatus).to.be('active'); + }); + + it('should find the created rule with correct information about the consumer', async () => { + const match = await alertingApi.findRule(ruleId); + expect(match).not.to.be(undefined); + expect(match.consumer).to.be('observability'); + }); + + it('should set correct information in the alert document', async () => { + const resp = await alertingApi.waitForAlertInIndex({ + indexName: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, + ruleId, + }); + + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.rule.category', + 'Custom threshold (Beta)' + ); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'observability'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.rule.rule_type_id', + 'observability.rules.custom_threshold' + ); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.uuid', ruleId); + expect(resp.hits.hits[0]._source).property('kibana.space_ids').contain('default'); + expect(resp.hits.hits[0]._source) + .property('kibana.alert.rule.tags') + .contain('observability'); + expect(resp.hits.hits[0]._source).property( + 'kibana.alert.action_group', + 'custom_threshold.fired' + ); + expect(resp.hits.hits[0]._source).property('tags').contain('observability'); + expect(resp.hits.hits[0]._source).property('kibana.alert.instance.id', '*'); + expect(resp.hits.hits[0]._source).property('kibana.alert.workflow_status', 'open'); + expect(resp.hits.hits[0]._source).property('event.kind', 'signal'); + expect(resp.hits.hits[0]._source).property('event.action', 'open'); + + expect(resp.hits.hits[0]._source) + .property('kibana.alert.rule.parameters') + .eql({ + criteria: [ + { + comparator: '>', + threshold: [1], + timeSize: 5, + timeUnit: 'm', + metrics: [{ name: 'A', field: 'system.network.in.bytes', aggType: 'p99' }], + }, + ], + alertOnNoData: true, + alertOnGroupDisappear: true, + searchConfiguration: { index: 'data-view-id', query: { query: '', language: 'kuery' } }, + }); + }); + }); + }); +} diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/fleet/fleet.ts b/x-pack/test_serverless/api_integration/test_suites/observability/fleet/fleet.ts index 98866bc50f431..73f582f9aa9cb 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/fleet/fleet.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/fleet/fleet.ts @@ -12,7 +12,8 @@ export default function ({ getService }: FtrProviderContext) { const svlCommonApi = getService('svlCommonApi'); const supertest = getService('supertest'); - describe('fleet', function () { + // Failing: See https://github.com/elastic/kibana/issues/176352 + describe.skip('fleet', function () { it('rejects request to create a new fleet server hosts if host url is different from default', async () => { const { body, status } = await supertest .post('/api/fleet/fleet_server_hosts') diff --git a/x-pack/test_serverless/api_integration/test_suites/security/fleet/fleet.ts b/x-pack/test_serverless/api_integration/test_suites/security/fleet/fleet.ts index 98866bc50f431..ba184e7687794 100644 --- a/x-pack/test_serverless/api_integration/test_suites/security/fleet/fleet.ts +++ b/x-pack/test_serverless/api_integration/test_suites/security/fleet/fleet.ts @@ -12,7 +12,8 @@ export default function ({ getService }: FtrProviderContext) { const svlCommonApi = getService('svlCommonApi'); const supertest = getService('supertest'); - describe('fleet', function () { + // FLAKY: https://github.com/elastic/kibana/issues/176399 + describe.skip('fleet', function () { it('rejects request to create a new fleet server hosts if host url is different from default', async () => { const { body, status } = await supertest .post('/api/fleet/fleet_server_hosts') diff --git a/x-pack/test_serverless/functional/test_suites/common/management/advanced_settings.ts b/x-pack/test_serverless/functional/test_suites/common/management/advanced_settings.ts index 4e93ebcf64b76..3b07f05fa9191 100644 --- a/x-pack/test_serverless/functional/test_suites/common/management/advanced_settings.ts +++ b/x-pack/test_serverless/functional/test_suites/common/management/advanced_settings.ts @@ -36,7 +36,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const kibanaServer = getService('kibanaServer'); let INITIAL_CSV_QUOTE_VALUES_SETTING_VALUE: any; - describe('Common advanced settings', function () { + // FLAKY: https://github.com/elastic/kibana/issues/172990 + describe.skip('Common advanced settings', function () { // the suite is flaky on MKI this.tags(['failsOnMKI']); before(async () => { diff --git a/x-pack/test_serverless/functional/test_suites/observability/cases/configure.ts b/x-pack/test_serverless/functional/test_suites/observability/cases/configure.ts index 2da11394dab17..63cc955cfb4d0 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/cases/configure.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/cases/configure.ts @@ -80,7 +80,7 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { await testSubjects.setValue('custom-field-label-input', 'Summary'); - await testSubjects.setCheckbox('text-custom-field-options-wrapper', 'check'); + await testSubjects.setCheckbox('text-custom-field-required-wrapper', 'check'); await testSubjects.click('custom-field-flyout-save'); expect(await testSubjects.exists('euiFlyoutCloseButton')).to.be(false); diff --git a/x-pack/test_serverless/functional/test_suites/observability/cases/create_case_form.ts b/x-pack/test_serverless/functional/test_suites/observability/cases/create_case_form.ts index 9bc03b8e232b8..c1b2f1e0068a5 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/cases/create_case_form.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/cases/create_case_form.ts @@ -80,14 +80,14 @@ export default ({ getService, getPageObject }: FtrProviderContext) => { { key: 'valid_key_1', label: 'Summary', - type: CustomFieldTypes.TEXT, - required: true, + type: CustomFieldTypes.TEXT as const, + required: false, }, { key: 'valid_key_2', label: 'Sync', - type: CustomFieldTypes.TOGGLE, - required: true, + type: CustomFieldTypes.TOGGLE as const, + required: false, }, ]; diff --git a/x-pack/test_serverless/functional/test_suites/observability/cases/view_case.ts b/x-pack/test_serverless/functional/test_suites/observability/cases/view_case.ts index 0e60fa0125234..2386d2b0e9585 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/cases/view_case.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/cases/view_case.ts @@ -462,13 +462,15 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { { key: 'valid_key_1', label: 'Summary', - type: CustomFieldTypes.TEXT, + type: CustomFieldTypes.TEXT as const, + defaultValue: 'foobar', required: true, }, { key: 'valid_key_2', label: 'Sync', - type: CustomFieldTypes.TOGGLE, + type: CustomFieldTypes.TOGGLE as const, + defaultValue: false, required: true, }, ]; diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/cases/configure.ts b/x-pack/test_serverless/functional/test_suites/security/ftr/cases/configure.ts index 373782e69bb67..fe14d0f9aef4c 100644 --- a/x-pack/test_serverless/functional/test_suites/security/ftr/cases/configure.ts +++ b/x-pack/test_serverless/functional/test_suites/security/ftr/cases/configure.ts @@ -80,7 +80,7 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { await testSubjects.setValue('custom-field-label-input', 'Summary'); - await testSubjects.setCheckbox('text-custom-field-options-wrapper', 'check'); + await testSubjects.setCheckbox('text-custom-field-required-wrapper', 'check'); await testSubjects.click('custom-field-flyout-save'); expect(await testSubjects.exists('euiFlyoutCloseButton')).to.be(false); diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/cases/create_case_form.ts b/x-pack/test_serverless/functional/test_suites/security/ftr/cases/create_case_form.ts index b5ef129b467dc..27e4fda20f5ec 100644 --- a/x-pack/test_serverless/functional/test_suites/security/ftr/cases/create_case_form.ts +++ b/x-pack/test_serverless/functional/test_suites/security/ftr/cases/create_case_form.ts @@ -80,14 +80,14 @@ export default ({ getService, getPageObject }: FtrProviderContext) => { { key: 'valid_key_1', label: 'Summary', - type: CustomFieldTypes.TEXT, - required: true, + type: CustomFieldTypes.TEXT as const, + required: false, }, { key: 'valid_key_2', label: 'Sync', - type: CustomFieldTypes.TOGGLE, - required: true, + type: CustomFieldTypes.TOGGLE as const, + required: false, }, ]; diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/cases/view_case.ts b/x-pack/test_serverless/functional/test_suites/security/ftr/cases/view_case.ts index d9429f93bc9af..94329bc5b48dc 100644 --- a/x-pack/test_serverless/functional/test_suites/security/ftr/cases/view_case.ts +++ b/x-pack/test_serverless/functional/test_suites/security/ftr/cases/view_case.ts @@ -462,13 +462,15 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { { key: 'valid_key_1', label: 'Summary', - type: CustomFieldTypes.TEXT, + type: CustomFieldTypes.TEXT as const, + defaultValue: 'foobar', required: true, }, { key: 'valid_key_2', label: 'Sync', - type: CustomFieldTypes.TOGGLE, + type: CustomFieldTypes.TOGGLE as const, + defaultValue: false, required: true, }, ]; diff --git a/yarn.lock b/yarn.lock index 0e69b838186d2..9b39abe1b4587 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4348,6 +4348,10 @@ version "0.0.0" uid "" +"@kbn/data-view-utils@link:packages/kbn-data-view-utils": + version "0.0.0" + uid "" + "@kbn/data-views-plugin@link:src/plugins/data_views": version "0.0.0" uid "" @@ -26016,13 +26020,6 @@ react-resizable@^3.0.4: prop-types "15.x" react-draggable "^4.0.3" -react-resize-detector@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/react-resize-detector/-/react-resize-detector-7.1.1.tgz#18d5b84909d5ab13abe0a68ddf0fb8e80c553dfc" - integrity sha512-rU54VTstNzFLZAmMNHqt8xINjDWP7SQR05A2HUW0OGvl4vcrXzgaxrrqAY5tZMfkLkoYm5u0i0qGqCjdc2jyAA== - dependencies: - lodash "^4.17.21" - react-reverse-portal@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/react-reverse-portal/-/react-reverse-portal-2.1.0.tgz#3c572e1c0d9e49b8febf4bf2fd43b9819ce6f508" @@ -32071,10 +32068,10 @@ zip-stream@^4.1.0: compress-commons "^4.1.0" readable-stream "^3.6.0" -zod-to-json-schema@^3.20.4: - version "3.21.4" - resolved "https://registry.yarnpkg.com/zod-to-json-schema/-/zod-to-json-schema-3.21.4.tgz#de97c5b6d4a25e9d444618486cb55c0c7fb949fd" - integrity sha512-fjUZh4nQ1s6HMccgIeE0VP4QG/YRGPmyjO9sAh890aQKPEk3nqbfUXhMFaC+Dr5KvYBm8BCyvfpZf2jY9aGSsw== +zod-to-json-schema@^3.20.4, zod-to-json-schema@^3.22.3: + version "3.22.3" + resolved "https://registry.yarnpkg.com/zod-to-json-schema/-/zod-to-json-schema-3.22.3.tgz#1c71f9fa23f80b2f3b5eed537afa8a13a66a5200" + integrity sha512-9isG8SqRe07p+Aio2ruBZmLm2Q6Sq4EqmXOiNpDxp+7f0LV6Q/LX65fs5Nn+FV/CzfF3NLBoksXbS2jNYIfpKw== zod@^3.22.3: version "3.22.3"