diff --git a/.buildkite/ftr_configs.yml b/.buildkite/ftr_configs.yml index c5028cd30ea29f..cc67d14eefcf83 100644 --- a/.buildkite/ftr_configs.yml +++ b/.buildkite/ftr_configs.yml @@ -16,6 +16,7 @@ disabled: - x-pack/test/security_solution_api_integration/config/ess/config.base.ts - x-pack/test/security_solution_api_integration/config/serverless/config.base.ts - x-pack/test/security_solution_endpoint/config.base.ts + - x-pack/test/security_solution_endpoint_api_int/config.base.ts # QA suites that are run out-of-band - x-pack/test/stack_functional_integration/configs/config.stack_functional_integration_base.js @@ -227,10 +228,6 @@ enabled: - x-pack/test/detection_engine_api_integration/security_and_spaces/group5/config.ts - x-pack/test/detection_engine_api_integration/security_and_spaces/group10/config.ts - x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/config.ts - - x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/config.ts - - x-pack/test/detection_engine_api_integration/security_and_spaces/bundled_prebuilt_rules_package/config.ts - - x-pack/test/detection_engine_api_integration/security_and_spaces/large_prebuilt_rules_package/config.ts - - x-pack/test/detection_engine_api_integration/security_and_spaces/update_prebuilt_rules_package/config.ts - x-pack/test/disable_ems/config.ts - x-pack/test/encrypted_saved_objects_api_integration/config.ts - x-pack/test/examples/config.ts @@ -385,6 +382,7 @@ enabled: - x-pack/test/security_functional/user_profiles.config.ts - x-pack/test/security_functional/expired_session.config.ts - x-pack/test/security_solution_endpoint_api_int/config.ts + - x-pack/test/security_solution_endpoint_api_int/serverless.config.ts - x-pack/test/security_solution_endpoint/endpoint.config.ts - x-pack/test/security_solution_endpoint/serverless.endpoint.config.ts - x-pack/test/security_solution_endpoint/integrations.config.ts @@ -462,8 +460,13 @@ enabled: - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/configs/ess.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/configs/serverless.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/configs/ess.config.ts - - - - - + - x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/configs/serverless.config.ts + - x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/configs/ess.config.ts + - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/management/configs/serverless.config.ts + - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/management/configs/ess.config.ts + - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/bundled_prebuilt_rules_package/configs/serverless.config.ts + - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/bundled_prebuilt_rules_package/configs/ess.config.ts + - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/large_prebuilt_rules_package/configs/serverless.config.ts + - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/large_prebuilt_rules_package/configs/ess.config.ts + - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/update_prebuilt_rules_package/configs/serverless.config.ts + - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/update_prebuilt_rules_package/configs/ess.config.ts diff --git a/.buildkite/pipelines/es_serverless/emergency_relelease_branch_testing.yml b/.buildkite/pipelines/es_serverless/emergency_relelease_branch_testing.yml new file mode 100644 index 00000000000000..8fc5da666d56bd --- /dev/null +++ b/.buildkite/pipelines/es_serverless/emergency_relelease_branch_testing.yml @@ -0,0 +1,17 @@ +# https://buildkite.com/elastic/kibana-serverless-emergency-release-branch-testing + +## Triggers the artifacts container image build for emergency releases +agents: + queue: kibana-default + +notify: + - slack: "#kibana-mission-control" + if: "build.state == 'passed' || build.state == 'failed' || build.state == 'scheduled'" + +steps: + - trigger: "kibana-artifacts-container-image" + label: ":docker: Build Kibana Artifacts Container Image" + build: + branch: $BUILDKITE_BRANCH + commit: $BUILDKITE_COMMIT + message: Running PR build for $BUILDKITE_BRANCH diff --git a/.buildkite/pipelines/security_solution/api_integration.yml b/.buildkite/pipelines/security_solution/api_integration.yml new file mode 100644 index 00000000000000..b4c6cece31c4b1 --- /dev/null +++ b/.buildkite/pipelines/security_solution/api_integration.yml @@ -0,0 +1,67 @@ +steps: + - label: Running exception_workflows:runner:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh exception_workflows:qa:serverless + key: exception_workflows:runner:serverless + agents: + queue: n2-4-spot + timeout_in_minutes: 120 + retry: + automatic: + - exit_status: '*' + limit: 2 + + - label: Running exception_operators_date_numeric_types:runner: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:runner:serverless + agents: + queue: n2-4-spot + timeout_in_minutes: 120 + retry: + automatic: + - exit_status: '*' + limit: 2 + + - label: Running exception_operators_keyword_text_long:runner:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh exception_operators_keyword_text_long:qa:serverless + key: exception_operators_keyword_text_long:runner:serverless + agents: + queue: n2-4-spot + timeout_in_minutes: 120 + retry: + automatic: + - exit_status: '*' + limit: 2 + + - label: Running exception_operators_ips_text_array:runner:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh exception_operators_ips_text_array:qa:serverless + key: exception_operators_ips_text_array:runner:serverless + agents: + queue: n2-4-spot + timeout_in_minutes: 120 + retry: + automatic: + - exit_status: '1' + limit: 2 + + - label: Running rule_creation:runner:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh rule_creation:qa:serverless + key: rule_creation:runner: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 + diff --git a/.buildkite/pipelines/security_solution/base.yml b/.buildkite/pipelines/security_solution/security_solution_cypress.yml similarity index 79% rename from .buildkite/pipelines/security_solution/base.yml rename to .buildkite/pipelines/security_solution/security_solution_cypress.yml index 337c44ccdcc7e4..247505ef1c85a6 100644 --- a/.buildkite/pipelines/security_solution/base.yml +++ b/.buildkite/pipelines/security_solution/security_solution_cypress.yml @@ -1,5 +1,5 @@ steps: - - command: .buildkite/scripts/pipelines/security_solution_quality_gate/mki_security_solution_cypress.sh cypress:run:qa:serverless + - command: .buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh cypress:run:qa:serverless label: 'Serverless MKI QA Security Cypress Tests' agents: queue: n2-4-spot @@ -11,7 +11,7 @@ steps: - exit_status: '*' limit: 1 - - command: .buildkite/scripts/pipelines/security_solution_quality_gate/mki_security_solution_cypress.sh cypress:run:qa:serverless:explore + - 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 @@ -23,7 +23,7 @@ steps: - exit_status: '*' limit: 1 - - command: .buildkite/scripts/pipelines/security_solution_quality_gate/mki_security_solution_cypress.sh cypress:run:qa:serverless:investigations + - 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 diff --git a/.buildkite/scripts/common/env.sh b/.buildkite/scripts/common/env.sh index ccaeb7b0dc5d90..89c9dce5a3b8fe 100755 --- a/.buildkite/scripts/common/env.sh +++ b/.buildkite/scripts/common/env.sh @@ -28,6 +28,24 @@ export KIBANA_BASE_BRANCH="$KIBANA_PKG_BRANCH" KIBANA_PKG_VERSION="$(jq -r .version "$KIBANA_DIR/package.json")" export KIBANA_PKG_VERSION +# Detects and exports the final target branch when using a merge queue +if [[ "${BUILDKITE_BRANCH:-}" == "gh-readonly-queue"* ]]; then + # removes gh-readonly-queue/ + BKBRANCH_WITHOUT_GH_MQ_PREFIX="${BUILDKITE_BRANCH#gh-readonly-queue/}" + + # extracts target mqueue branch + MERGE_QUEUE_TARGET_BRANCH=${BKBRANCH_WITHOUT_GH_MQ_PREFIX%/*} +else + MERGE_QUEUE_TARGET_BRANCH="" +fi +export MERGE_QUEUE_TARGET_BRANCH + +# Exports BUILDKITE_BRANCH_MERGE_QUEUE which will use the value from MERGE_QUEUE_TARGET_BRANCH if defined otherwise +# will fallback to BUILDKITE_BRANCH. +BUILDKITE_BRANCH_MERGE_QUEUE="${MERGE_QUEUE_TARGET_BRANCH:-${BUILDKITE_BRANCH:-}}" +export BUILDKITE_BRANCH_MERGE_QUEUE + + BUILDKITE_AGENT_GCP_REGION="" if [[ "$(curl -is metadata.google.internal || true)" ]]; then # projects/1003139005402/zones/us-central1-a -> us-central1-a -> us-central1 diff --git a/.buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh b/.buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh new file mode 100755 index 00000000000000..ad7e488dfaea35 --- /dev/null +++ b/.buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh @@ -0,0 +1,82 @@ +#!/bin/bash +if [ -z "$1" ] + then + echo "No target script from the package.json file, is supplied" + exit 1 +fi + +source .buildkite/scripts/common/util.sh +.buildkite/scripts/bootstrap.sh + +buildkite-agent meta-data set "${BUILDKITE_JOB_ID}_is_test_execution_step" "true" + +echo "--- Serverless Security Second Quality Gate" +cd x-pack/test/security_solution_api_integration +set +e + +QA_API_KEY=$(retry 5 5 vault read -field=qa_api_key secret/kibana-issues/dev/security-solution-qg-enc-key) + +# 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' \ + --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=$(echo $ENVIRONMENT_DETAILS | jq -r '.name') +ID=$(echo $ENVIRONMENT_DETAILS | jq -r '.id') +ES_URL=$(echo $ENVIRONMENT_DETAILS | jq -r '.endpoints.elasticsearch') +KB_URL=$(echo $ENVIRONMENT_DETAILS | jq -r '.endpoints.kibana') + +# Wait five seconds for the project to appear +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" \ + --header "Authorization: ApiKey $QA_API_KEY" \ + --header 'Content-Type: application/json' | jq '.') +USERNAME=$(echo $CREDS_BODY | jq -r '.username') +PASSWORD=$(echo $CREDS_BODY | jq -r '.password') +AUTH=$(echo "$USERNAME:$PASSWORD") + +# Checking if Elasticsearch has status green +while : ; do + STATUS=$(curl -u $AUTH --location "$ES_URL:443/_cluster/health?wait_for_status=green&timeout=50s" | jq -r '.status') + if [ "$STATUS" != "green" ]; then + echo "Sleeping for 40s to wait for ES status to be green..." + sleep 40 + else + echo "Elasticsearch has status green." + break + fi +done + +# Checking if Kibana is available +while : ; do + STATUS=$(curl -u $AUTH --location "$KB_URL:443/api/status" | jq -r '.status.overall.level') + if [ "$STATUS" != "available" ]; then + echo "Sleeping for 15s to wait for Kibana to be available..." + sleep 15 + else + echo "Kibana is available." + break + fi +done + +# Removing the https:// part of the url provided in order to use it in the command below. +FORMATTED_ES_URL="${ES_URL/https:\/\//}" +FORMATTED_KB_URL="${KB_URL/https:\/\//}" + +# Find a way to remove this in the future +# This is used in order to wait for the environment to be ready. +sleep 150 + +TEST_CLOUD=1 TEST_ES_URL="https://elastic:$PASSWORD@$FORMATTED_ES_URL:443" TEST_KIBANA_URL="https://elastic:$PASSWORD@$FORMATTED_KB_URL:443" yarn run $1 +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" \ + --header "Authorization: ApiKey $QA_API_KEY" + +exit $cmd_status \ No newline at end of file diff --git a/.buildkite/scripts/pipelines/security_solution_quality_gate/edr_workflows/pipeline.sh b/.buildkite/scripts/pipelines/security_solution_quality_gate/edr_workflows/pipeline.sh new file mode 100755 index 00000000000000..807ec48ab48ed7 --- /dev/null +++ b/.buildkite/scripts/pipelines/security_solution_quality_gate/edr_workflows/pipeline.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +set -euo pipefail + +echo "Running the EDR-Workflows testing for Kibana" \ No newline at end of file diff --git a/.buildkite/scripts/pipelines/security_solution_quality_gate/mki_security_solution_cypress.sh b/.buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh similarity index 100% rename from .buildkite/scripts/pipelines/security_solution_quality_gate/mki_security_solution_cypress.sh rename to .buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh diff --git a/.buildkite/scripts/pipelines/security_solution_quality_gate/pipeline.sh b/.buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/pipeline.sh similarity index 69% rename from .buildkite/scripts/pipelines/security_solution_quality_gate/pipeline.sh rename to .buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/pipeline.sh index 3f4b2093b807ed..b3d93e083fa412 100755 --- a/.buildkite/scripts/pipelines/security_solution_quality_gate/pipeline.sh +++ b/.buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/pipeline.sh @@ -2,4 +2,4 @@ set -euo pipefail -ts-node .buildkite/scripts/pipelines/security_solution_quality_gate/pipeline.ts +ts-node .buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/pipeline.ts diff --git a/.buildkite/scripts/pipelines/security_solution_quality_gate/pipeline.ts b/.buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/pipeline.ts similarity index 90% rename from .buildkite/scripts/pipelines/security_solution_quality_gate/pipeline.ts rename to .buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/pipeline.ts index 4084696d5c21ca..fb9ec67fba8883 100644 --- a/.buildkite/scripts/pipelines/security_solution_quality_gate/pipeline.ts +++ b/.buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/pipeline.ts @@ -28,7 +28,9 @@ const uploadPipeline = (pipelineContent: string | object) => { try { const pipeline = []; - pipeline.push(getPipeline('.buildkite/pipelines/security_solution/base.yml', false)); + pipeline.push( + getPipeline('.buildkite/pipelines/security_solution/security_solution_cypress.yml', false) + ); // remove duplicated steps uploadPipeline([...new Set(pipeline)].join('\n')); } catch (ex) { diff --git a/.buildkite/scripts/steps/artifacts/docker_context.sh b/.buildkite/scripts/steps/artifacts/docker_context.sh index 39e299251cac04..b6fe4834465fc6 100755 --- a/.buildkite/scripts/steps/artifacts/docker_context.sh +++ b/.buildkite/scripts/steps/artifacts/docker_context.sh @@ -11,7 +11,7 @@ KIBANA_DOCKER_CONTEXT="${KIBANA_DOCKER_CONTEXT:="default"}" echo "--- Create contexts" mkdir -p target -node scripts/build --skip-initialize --skip-generic-folders --skip-platform-folders --skip-archives --docker-context-use-local-artifact +node scripts/build --skip-initialize --skip-generic-folders --skip-platform-folders --skip-archives --skip-cdn-assets --docker-context-use-local-artifact "${BUILD_ARGS[@]}" echo "--- Setup context" DOCKER_BUILD_FOLDER=$(mktemp -d) diff --git a/.buildkite/scripts/steps/cloud/build_and_deploy.sh b/.buildkite/scripts/steps/cloud/build_and_deploy.sh index 40c223265856e2..9876ea52fdd3b6 100755 --- a/.buildkite/scripts/steps/cloud/build_and_deploy.sh +++ b/.buildkite/scripts/steps/cloud/build_and_deploy.sh @@ -37,6 +37,7 @@ else --skip-generic-folders \ --skip-platform-folders \ --skip-archives \ + --skip-cdn-assets \ --docker-images \ --docker-tag-qualifier="$GIT_COMMIT" \ --docker-push \ diff --git a/.buildkite/scripts/steps/serverless/build_and_deploy.sh b/.buildkite/scripts/steps/serverless/build_and_deploy.sh index 41a8880706b3de..6c5b72a0b49227 100644 --- a/.buildkite/scripts/steps/serverless/build_and_deploy.sh +++ b/.buildkite/scripts/steps/serverless/build_and_deploy.sh @@ -6,91 +6,102 @@ set -euo pipefail source .buildkite/scripts/common/util.sh source .buildkite/scripts/steps/artifacts/docker_image.sh -PROJECT_TYPE="" -is_pr_with_label "ci:project-deploy-elasticsearch" && PROJECT_TYPE="elasticsearch" -is_pr_with_label "ci:project-deploy-observability" && PROJECT_TYPE="observability" -is_pr_with_label "ci:project-deploy-security" && PROJECT_TYPE="security" -if [ -z "${PROJECT_TYPE}" ]; then - echo "Mising project type" - exit 10 -fi - -PROJECT_NAME="kibana-pr-$BUILDKITE_PULL_REQUEST-$PROJECT_TYPE" -PROJECT_CREATE_CONFIGURATION='{ - "name": "'"$PROJECT_NAME"'", - "region_id": "aws-eu-west-1", - "overrides": { - "kibana": { - "docker_image": "'"$KIBANA_IMAGE"'" - } - } -}' -PROJECT_UPDATE_CONFIGURATION='{ - "name": "'"$PROJECT_NAME"'", - "overrides": { - "kibana": { - "docker_image": "'"$KIBANA_IMAGE"'" - } - } -}' - -echo "--- Create project" -DEPLOY_LOGS=$(mktemp --suffix ".json") - -echo "Checking if project already exists..." -curl -s \ - -H "Authorization: ApiKey $PROJECT_API_KEY" \ - "${PROJECT_API_DOMAIN}/api/v1/serverless/projects/${PROJECT_TYPE}" \ - -XGET &>> $DEPLOY_LOGS - -PROJECT_ID=$(jq -r --slurp '[.[0].items[] | select(.name == "'$PROJECT_NAME'")] | .[0].id' $DEPLOY_LOGS) -if [ -z "${PROJECT_ID}" ] || [ "$PROJECT_ID" = 'null' ]; then - echo "Creating project..." - curl -s \ - -H "Authorization: ApiKey $PROJECT_API_KEY" \ - -H "Content-Type: application/json" \ - "${PROJECT_API_DOMAIN}/api/v1/serverless/projects/${PROJECT_TYPE}" \ - -XPOST -d "$PROJECT_CREATE_CONFIGURATION" &>> $DEPLOY_LOGS - - PROJECT_ID=$(jq -r --slurp '.[1].id' $DEPLOY_LOGS) - echo "Get credentials..." - curl -s -XPOST -H "Authorization: ApiKey $PROJECT_API_KEY" \ - "${PROJECT_API_DOMAIN}/api/v1/serverless/projects/${PROJECT_TYPE}/${PROJECT_ID}/_reset-credentials" &>> $DEPLOY_LOGS - - PROJECT_USERNAME=$(jq -r --slurp '.[2].username' $DEPLOY_LOGS) - PROJECT_PASSWORD=$(jq -r --slurp '.[2].password' $DEPLOY_LOGS) - - echo "Write to vault..." - VAULT_ROLE_ID="$(retry 5 15 gcloud secrets versions access latest --secret=kibana-buildkite-vault-role-id)" - VAULT_SECRET_ID="$(retry 5 15 gcloud secrets versions access latest --secret=kibana-buildkite-vault-secret-id)" - VAULT_TOKEN=$(retry 5 30 vault write -field=token auth/approle/login role_id="$VAULT_ROLE_ID" secret_id="$VAULT_SECRET_ID") - retry 5 30 vault login -no-print "$VAULT_TOKEN" - retry 5 5 vault write "secret/kibana-issues/dev/cloud-deploy/$PROJECT_NAME" username="$PROJECT_USERNAME" password="$PROJECT_PASSWORD" id="$PROJECT_ID" -else - echo "Updating project..." +deploy() { + PROJECT_TYPE=$1 + case $PROJECT_TYPE in + elasticsearch) + PROJECT_TYPE_LABEL='Elasticsearch Serverless' + ;; + observability) + PROJECT_TYPE_LABEL='Observability' + ;; + security) + PROJECT_TYPE_LABEL='Security' + ;; + esac + + PROJECT_NAME="kibana-pr-$BUILDKITE_PULL_REQUEST-$PROJECT_TYPE" + PROJECT_CREATE_CONFIGURATION='{ + "name": "'"$PROJECT_NAME"'", + "region_id": "aws-eu-west-1", + "overrides": { + "kibana": { + "docker_image": "'"$KIBANA_IMAGE"'" + } + } + }' + PROJECT_UPDATE_CONFIGURATION='{ + "name": "'"$PROJECT_NAME"'", + "overrides": { + "kibana": { + "docker_image": "'"$KIBANA_IMAGE"'" + } + } + }' + + echo "--- Create $PROJECT_TYPE_LABEL project" + DEPLOY_LOGS=$(mktemp --suffix ".json") + + echo "Checking if project already exists..." curl -s \ -H "Authorization: ApiKey $PROJECT_API_KEY" \ - -H "Content-Type: application/json" \ - "${PROJECT_API_DOMAIN}/api/v1/serverless/projects/${PROJECT_TYPE}/${PROJECT_ID}" \ - -XPUT -d "$PROJECT_UPDATE_CONFIGURATION" &>> $DEPLOY_LOGS -fi - -PROJECT_KIBANA_URL=$(jq -r --slurp '.[1].endpoints.kibana' $DEPLOY_LOGS) -PROJECT_KIBANA_LOGIN_URL="${PROJECT_KIBANA_URL}/login" -PROJECT_ELASTICSEARCH_URL=$(jq -r --slurp '.[1].endpoints.elasticsearch' $DEPLOY_LOGS) - -cat << EOF | buildkite-agent annotate --style "info" --context project - ### Project Deployment - - Kibana: $PROJECT_KIBANA_LOGIN_URL - - Elasticsearch: $PROJECT_ELASTICSEARCH_URL - - Credentials: \`vault read secret/kibana-issues/dev/cloud-deploy/$PROJECT_NAME\` - - Kibana image: \`$KIBANA_IMAGE\` + "${PROJECT_API_DOMAIN}/api/v1/serverless/projects/${PROJECT_TYPE}" \ + -XGET &>> $DEPLOY_LOGS + + PROJECT_ID=$(jq -r --slurp '[.[0].items[] | select(.name == "'$PROJECT_NAME'")] | .[0].id' $DEPLOY_LOGS) + if [ -z "${PROJECT_ID}" ] || [ "$PROJECT_ID" = 'null' ]; then + echo "Creating project..." + curl -s \ + -H "Authorization: ApiKey $PROJECT_API_KEY" \ + -H "Content-Type: application/json" \ + "${PROJECT_API_DOMAIN}/api/v1/serverless/projects/${PROJECT_TYPE}" \ + -XPOST -d "$PROJECT_CREATE_CONFIGURATION" &>> $DEPLOY_LOGS + + PROJECT_ID=$(jq -r --slurp '.[1].id' $DEPLOY_LOGS) + + echo "Get credentials..." + curl -s -XPOST -H "Authorization: ApiKey $PROJECT_API_KEY" \ + "${PROJECT_API_DOMAIN}/api/v1/serverless/projects/${PROJECT_TYPE}/${PROJECT_ID}/_reset-credentials" &>> $DEPLOY_LOGS + + PROJECT_USERNAME=$(jq -r --slurp '.[2].username' $DEPLOY_LOGS) + PROJECT_PASSWORD=$(jq -r --slurp '.[2].password' $DEPLOY_LOGS) + + echo "Write to vault..." + VAULT_ROLE_ID="$(retry 5 15 gcloud secrets versions access latest --secret=kibana-buildkite-vault-role-id)" + VAULT_SECRET_ID="$(retry 5 15 gcloud secrets versions access latest --secret=kibana-buildkite-vault-secret-id)" + VAULT_TOKEN=$(retry 5 30 vault write -field=token auth/approle/login role_id="$VAULT_ROLE_ID" secret_id="$VAULT_SECRET_ID") + retry 5 30 vault login -no-print "$VAULT_TOKEN" + retry 5 5 vault write "secret/kibana-issues/dev/cloud-deploy/$PROJECT_NAME" username="$PROJECT_USERNAME" password="$PROJECT_PASSWORD" id="$PROJECT_ID" + else + echo "Updating project..." + curl -s \ + -H "Authorization: ApiKey $PROJECT_API_KEY" \ + -H "Content-Type: application/json" \ + "${PROJECT_API_DOMAIN}/api/v1/serverless/projects/${PROJECT_TYPE}/${PROJECT_ID}" \ + -XPUT -d "$PROJECT_UPDATE_CONFIGURATION" &>> $DEPLOY_LOGS + fi + + PROJECT_KIBANA_URL=$(jq -r --slurp '.[1].endpoints.kibana' $DEPLOY_LOGS) + PROJECT_KIBANA_LOGIN_URL="${PROJECT_KIBANA_URL}/login" + PROJECT_ELASTICSEARCH_URL=$(jq -r --slurp '.[1].endpoints.elasticsearch' $DEPLOY_LOGS) + + cat << EOF | buildkite-agent annotate --style "info" --context "project-$PROJECT_TYPE" +### $PROJECT_TYPE_LABEL Deployment + +Kibana: $PROJECT_KIBANA_LOGIN_URL + +Elasticsearch: $PROJECT_ELASTICSEARCH_URL + +Credentials: \`vault read secret/kibana-issues/dev/cloud-deploy/$PROJECT_NAME\` + +Kibana image: \`$KIBANA_IMAGE\` EOF -buildkite-agent meta-data set pr_comment:deploy_project:head "* [Project Deployment](${PROJECT_KIBANA_LOGIN_URL})" -buildkite-agent meta-data set pr_comment:early_comment_job_id "$BUILDKITE_JOB_ID" + buildkite-agent meta-data set "pr_comment:deploy_project_$PROJECT_TYPE:head" "* [$PROJECT_TYPE_LABEL Deployment](${PROJECT_KIBANA_LOGIN_URL})" + buildkite-agent meta-data set pr_comment:early_comment_job_id "$BUILDKITE_JOB_ID" +} + +is_pr_with_label "ci:project-deploy-elasticsearch" && deploy "elasticsearch" +is_pr_with_label "ci:project-deploy-observability" && deploy "observability" +is_pr_with_label "ci:project-deploy-security" && deploy "security" diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 1dc3be1a66d6d0..453cbc463b270b 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -36,14 +36,14 @@ packages/analytics/shippers/elastic_v3/server @elastic/kibana-core packages/analytics/shippers/fullstory @elastic/kibana-core packages/analytics/shippers/gainsight @elastic/kibana-core packages/kbn-apm-config-loader @elastic/kibana-core @vigneshshanmugam -x-pack/plugins/apm_data_access @elastic/apm-ui -x-pack/plugins/apm @elastic/apm-ui -packages/kbn-apm-synthtrace @elastic/apm-ui -packages/kbn-apm-synthtrace-client @elastic/apm-ui -packages/kbn-apm-utils @elastic/apm-ui +x-pack/plugins/apm_data_access @elastic/obs-knowledge-team @elastic/obs-ux-infra_services-team +x-pack/plugins/apm @elastic/obs-ux-infra_services-team +packages/kbn-apm-synthtrace @elastic/obs-ux-infra_services-team +packages/kbn-apm-synthtrace-client @elastic/obs-ux-infra_services-team +packages/kbn-apm-utils @elastic/obs-ux-infra_services-team test/plugin_functional/plugins/app_link_test @elastic/kibana-core x-pack/test/usage_collection/plugins/application_usage_test @elastic/kibana-core -x-pack/plugins/asset_manager @elastic/infra-monitoring-ui +x-pack/plugins/asset_manager @elastic/obs-knowledge-team x-pack/test/security_api_integration/plugins/audit_log @elastic/kibana-security packages/kbn-axe-config @elastic/kibana-qa packages/kbn-babel-preset @elastic/kibana-operations @@ -300,7 +300,7 @@ x-pack/plugins/cross_cluster_replication @elastic/platform-deployment-management packages/kbn-crypto @elastic/kibana-security packages/kbn-crypto-browser @elastic/kibana-core x-pack/plugins/custom_branding @elastic/appex-sharedux -packages/kbn-custom-integrations @elastic/infra-monitoring-ui +packages/kbn-custom-integrations @elastic/obs-ux-logs-team src/plugins/custom_integrations @elastic/fleet packages/kbn-cypress-config @elastic/kibana-operations x-pack/plugins/dashboard_enhanced @elastic/kibana-presentation @@ -314,12 +314,13 @@ src/plugins/data_view_field_editor @elastic/kibana-data-discovery src/plugins/data_view_management @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 packages/kbn-datemath @elastic/kibana-data-discovery packages/deeplinks/analytics @elastic/kibana-data-discovery @elastic/kibana-presentation @elastic/kibana-visualizations packages/deeplinks/devtools @elastic/platform-deployment-management packages/deeplinks/management @elastic/platform-deployment-management packages/deeplinks/ml @elastic/ml-ui -packages/deeplinks/observability @elastic/apm-ui +packages/deeplinks/observability @elastic/obs-ux-logs-team packages/deeplinks/search @elastic/enterprise-search-frontend packages/default-nav/analytics @elastic/kibana-data-discovery @elastic/kibana-presentation @elastic/kibana-visualizations packages/default-nav/devtools @elastic/platform-deployment-management @@ -353,18 +354,19 @@ src/plugins/embeddable @elastic/kibana-presentation x-pack/examples/embedded_lens_example @elastic/kibana-visualizations x-pack/plugins/encrypted_saved_objects @elastic/kibana-security x-pack/plugins/enterprise_search @elastic/enterprise-search-frontend +examples/error_boundary @elastic/appex-sharedux packages/kbn-es @elastic/kibana-operations packages/kbn-es-archiver @elastic/kibana-operations @elastic/appex-qa packages/kbn-es-errors @elastic/kibana-core packages/kbn-es-query @elastic/kibana-data-discovery -packages/kbn-es-types @elastic/kibana-core @elastic/apm-ui +packages/kbn-es-types @elastic/kibana-core @elastic/obs-knowledge-team src/plugins/es_ui_shared @elastic/platform-deployment-management packages/kbn-eslint-config @elastic/kibana-operations packages/kbn-eslint-plugin-disable @elastic/kibana-operations packages/kbn-eslint-plugin-eslint @elastic/kibana-operations -packages/kbn-eslint-plugin-i18n @elastic/actionable-observability +packages/kbn-eslint-plugin-i18n @elastic/obs-knowledge-team packages/kbn-eslint-plugin-imports @elastic/kibana-operations -packages/kbn-eslint-plugin-telemetry @elastic/actionable-observability +packages/kbn-eslint-plugin-telemetry @elastic/obs-knowledge-team x-pack/test/encrypted_saved_objects_api_integration/plugins/api_consumer_plugin @elastic/kibana-security packages/kbn-event-annotation-common @elastic/kibana-visualizations packages/kbn-event-annotation-components @elastic/kibana-visualizations @@ -374,8 +376,8 @@ x-pack/test/plugin_api_integration/plugins/event_log @elastic/response-ops x-pack/plugins/event_log @elastic/response-ops packages/kbn-expandable-flyout @elastic/security-threat-hunting-investigations packages/kbn-expect @elastic/kibana-operations @elastic/appex-qa -x-pack/examples/exploratory_view_example @elastic/uptime -x-pack/plugins/exploratory_view @elastic/uptime +x-pack/examples/exploratory_view_example @elastic/obs-ux-infra_services-team +x-pack/plugins/exploratory_view @elastic/obs-ux-infra_services-team src/plugins/expression_error @elastic/kibana-presentation src/plugins/chart_expressions/expression_gauge @elastic/kibana-visualizations src/plugins/chart_expressions/expression_heatmap @elastic/kibana-visualizations @@ -444,15 +446,15 @@ packages/kbn-import-resolver @elastic/kibana-operations x-pack/plugins/index_lifecycle_management @elastic/platform-deployment-management x-pack/plugins/index_management @elastic/platform-deployment-management test/plugin_functional/plugins/index_patterns @elastic/kibana-data-discovery -x-pack/packages/kbn-infra-forge @elastic/actionable-observability -x-pack/plugins/infra @elastic/infra-monitoring-ui +x-pack/packages/kbn-infra-forge @elastic/obs-ux-management-team +x-pack/plugins/infra @elastic/infra-monitoring-ui @elastic/obs-ux-logs-team @elastic/obs-ux-infra_services-team x-pack/plugins/ingest_pipelines @elastic/platform-deployment-management src/plugins/input_control_vis @elastic/kibana-presentation src/plugins/inspector @elastic/kibana-presentation src/plugins/interactive_setup @elastic/kibana-security test/interactive_setup_api_integration/plugins/test_endpoints @elastic/kibana-security packages/kbn-interpreter @elastic/kibana-visualizations -packages/kbn-io-ts-utils @elastic/apm-ui +packages/kbn-io-ts-utils @elastic/obs-knowledge-team packages/kbn-jest-serializers @elastic/kibana-operations packages/kbn-journeys @elastic/kibana-operations @elastic/appex-qa packages/kbn-json-ast @elastic/kibana-operations @@ -469,7 +471,7 @@ src/plugins/kibana_usage_collection @elastic/kibana-core src/plugins/kibana_utils @elastic/appex-sharedux x-pack/plugins/kubernetes_security @elastic/kibana-cloud-security-posture packages/kbn-language-documentation-popover @elastic/kibana-visualizations -packages/kbn-lens-embeddable-utils @elastic/infra-monitoring-ui +packages/kbn-lens-embeddable-utils @elastic/obs-ux-infra_services-team x-pack/plugins/lens @elastic/kibana-visualizations x-pack/plugins/license_api_guard @elastic/platform-deployment-management x-pack/plugins/license_management @elastic/platform-deployment-management @@ -480,10 +482,10 @@ packages/kbn-lint-ts-projects-cli @elastic/kibana-operations x-pack/plugins/lists @elastic/security-detection-engine examples/locator_examples @elastic/appex-sharedux examples/locator_explorer @elastic/appex-sharedux -x-pack/plugins/log_explorer @elastic/infra-monitoring-ui +x-pack/plugins/log_explorer @elastic/obs-ux-logs-team packages/kbn-logging @elastic/kibana-core packages/kbn-logging-mocks @elastic/kibana-core -x-pack/plugins/logs_shared @elastic/infra-monitoring-ui +x-pack/plugins/logs_shared @elastic/obs-ux-logs-team x-pack/plugins/logstash @elastic/logstash packages/kbn-managed-vscode-config @elastic/kibana-operations packages/kbn-managed-vscode-config-cli @elastic/kibana-operations @@ -506,7 +508,7 @@ x-pack/examples/third_party_maps_source_example @elastic/kibana-gis src/plugins/maps_ems @elastic/kibana-gis x-pack/plugins/maps @elastic/kibana-gis x-pack/packages/maps/vector_tile_utils @elastic/kibana-gis -x-pack/plugins/metrics_data_access @elastic/infra-monitoring-ui +x-pack/plugins/metrics_data_access @elastic/obs-knowledge-team x-pack/packages/ml/agg_utils @elastic/ml-ui x-pack/packages/ml/anomaly_utils @elastic/ml-ui x-pack/packages/ml/category_validator @elastic/ml-ui @@ -533,24 +535,24 @@ x-pack/packages/ml/string_hash @elastic/ml-ui x-pack/packages/ml/trained_models_utils @elastic/ml-ui x-pack/packages/ml/url_state @elastic/ml-ui packages/kbn-monaco @elastic/appex-sharedux -x-pack/plugins/monitoring_collection @elastic/infra-monitoring-ui -x-pack/plugins/monitoring @elastic/infra-monitoring-ui +x-pack/plugins/monitoring_collection @elastic/obs-ux-infra_services-team +x-pack/plugins/monitoring @elastic/obs-ux-infra_services-team src/plugins/navigation @elastic/appex-sharedux src/plugins/newsfeed @elastic/kibana-core test/common/plugins/newsfeed @elastic/kibana-core src/plugins/no_data_page @elastic/appex-sharedux x-pack/plugins/notifications @elastic/appex-sharedux packages/kbn-object-versioning @elastic/appex-sharedux -x-pack/plugins/observability_ai_assistant @elastic/obs-ai-assistant +x-pack/plugins/observability_ai_assistant @elastic/obs-knowledge-team x-pack/packages/observability/alert_details @elastic/actionable-observability x-pack/packages/observability/alerting_test_data @elastic/actionable-observability x-pack/test/cases_api_integration/common/plugins/observability @elastic/response-ops -x-pack/plugins/observability_log_explorer @elastic/infra-monitoring-ui -x-pack/plugins/observability_onboarding @elastic/apm-ui -x-pack/plugins/observability @elastic/actionable-observability +x-pack/plugins/observability_log_explorer @elastic/obs-ux-logs-team +x-pack/plugins/observability_onboarding @elastic/obs-ux-logs-team +x-pack/plugins/observability @elastic/obs-ux-management-team x-pack/plugins/observability_shared @elastic/observability-ui x-pack/test/security_api_integration/plugins/oidc_provider @elastic/kibana-security -test/common/plugins/otel_metrics @elastic/infra-monitoring-ui +test/common/plugins/otel_metrics @elastic/obs-ux-infra_services-team packages/kbn-openapi-generator @elastic/security-detection-rule-management packages/kbn-optimizer @elastic/kibana-operations packages/kbn-optimizer-webpack-helpers @elastic/kibana-operations @@ -567,9 +569,9 @@ packages/kbn-plugin-helpers @elastic/kibana-operations examples/portable_dashboards_example @elastic/kibana-presentation examples/preboot_example @elastic/kibana-security @elastic/kibana-core src/plugins/presentation_util @elastic/kibana-presentation -x-pack/plugins/profiling_data_access @elastic/profiling-ui -x-pack/plugins/profiling @elastic/profiling-ui -packages/kbn-profiling-utils @elastic/profiling-ui +x-pack/plugins/profiling_data_access @elastic/obs-ux-infra_services-team +x-pack/plugins/profiling @elastic/obs-ux-infra_services-team +packages/kbn-profiling-utils @elastic/obs-ux-infra_services-team x-pack/packages/kbn-random-sampling @elastic/kibana-visualizations packages/kbn-react-field @elastic/kibana-data-discovery packages/react/kibana_context/common @elastic/appex-sharedux @@ -598,8 +600,8 @@ packages/kbn-rison @elastic/kibana-operations x-pack/plugins/rollup @elastic/platform-deployment-management examples/routing_example @elastic/kibana-core packages/kbn-rrule @elastic/response-ops -packages/kbn-rule-data-utils @elastic/security-detections-response @elastic/actionable-observability @elastic/response-ops -x-pack/plugins/rule_registry @elastic/response-ops @elastic/actionable-observability +packages/kbn-rule-data-utils @elastic/security-detections-response @elastic/response-ops @elastic/obs-ux-management-team +x-pack/plugins/rule_registry @elastic/response-ops @elastic/obs-ux-management-team x-pack/plugins/runtime_fields @elastic/platform-deployment-management packages/kbn-safer-lodash-set @elastic/kibana-security x-pack/test/security_api_integration/plugins/saml_provider @elastic/kibana-security @@ -656,11 +658,11 @@ packages/kbn-securitysolution-rules @elastic/security-detection-engine packages/kbn-securitysolution-t-grid @elastic/security-detection-engine packages/kbn-securitysolution-utils @elastic/security-detection-engine packages/kbn-server-http-tools @elastic/kibana-core -packages/kbn-server-route-repository @elastic/apm-ui +packages/kbn-server-route-repository @elastic/obs-knowledge-team @elastic/obs-ux-management-team x-pack/plugins/serverless @elastic/appex-sharedux packages/serverless/settings/common @elastic/appex-sharedux @elastic/platform-deployment-management -x-pack/plugins/serverless_observability @elastic/appex-sharedux @elastic/apm-ui -packages/serverless/settings/observability_project @elastic/appex-sharedux @elastic/apm-ui @elastic/platform-deployment-management +x-pack/plugins/serverless_observability @elastic/appex-sharedux @elastic/obs-ux-management-team +packages/serverless/settings/observability_project @elastic/appex-sharedux @elastic/platform-deployment-management @elastic/obs-ux-management-team packages/serverless/project_switcher @elastic/appex-sharedux x-pack/plugins/serverless_search @elastic/enterprise-search-frontend packages/serverless/settings/search_project @elastic/enterprise-search-frontend @elastic/platform-deployment-management @@ -672,7 +674,7 @@ x-pack/plugins/session_view @elastic/kibana-cloud-security-posture packages/kbn-set-map @elastic/kibana-operations examples/share_examples @elastic/appex-sharedux src/plugins/share @elastic/appex-sharedux -packages/kbn-shared-svg @elastic/apm-ui +packages/kbn-shared-svg @elastic/obs-ux-infra_services-team packages/shared-ux/avatar/solution @elastic/appex-sharedux packages/shared-ux/button/exit_full_screen @elastic/appex-sharedux packages/shared-ux/button_toolbar @elastic/appex-sharedux @@ -721,7 +723,7 @@ packages/shared-ux/router/types @elastic/appex-sharedux packages/shared-ux/storybook/config @elastic/appex-sharedux packages/shared-ux/storybook/mock @elastic/appex-sharedux packages/kbn-shared-ux-utility @elastic/appex-sharedux -x-pack/packages/kbn-slo-schema @elastic/actionable-observability +x-pack/packages/kbn-slo-schema @elastic/obs-ux-management-team x-pack/plugins/snapshot_restore @elastic/platform-deployment-management packages/kbn-some-dev-log @elastic/kibana-operations packages/kbn-sort-package-json @elastic/kibana-operations @@ -738,7 +740,7 @@ packages/kbn-std @elastic/kibana-core packages/kbn-stdio-dev-helpers @elastic/kibana-operations packages/kbn-storybook @elastic/kibana-operations packages/kbn-subscription-tracking @elastic/security-threat-hunting-investigations -x-pack/plugins/synthetics @elastic/uptime +x-pack/plugins/synthetics @elastic/obs-ux-infra_services-team x-pack/test/alerting_api_integration/common/plugins/task_manager_fixture @elastic/response-ops x-pack/test/plugin_api_perf/plugins/task_manager_performance @elastic/response-ops x-pack/plugins/task_manager @elastic/response-ops @@ -768,7 +770,7 @@ x-pack/examples/triggers_actions_ui_example @elastic/response-ops x-pack/plugins/triggers_actions_ui @elastic/response-ops packages/kbn-ts-projects @elastic/kibana-operations packages/kbn-ts-type-check-cli @elastic/kibana-operations -packages/kbn-typed-react-router-config @elastic/apm-ui +packages/kbn-typed-react-router-config @elastic/obs-knowledge-team @elastic/obs-ux-management-team packages/kbn-ui-actions-browser @elastic/appex-sharedux x-pack/examples/ui_actions_enhanced_examples @elastic/appex-sharedux src/plugins/ui_actions_enhanced @elastic/appex-sharedux @@ -788,20 +790,20 @@ examples/unified_field_list_examples @elastic/kibana-data-discovery src/plugins/unified_histogram @elastic/kibana-data-discovery src/plugins/unified_search @elastic/kibana-visualizations x-pack/plugins/upgrade_assistant @elastic/platform-deployment-management -x-pack/plugins/uptime @elastic/uptime +x-pack/plugins/uptime @elastic/obs-ux-infra_services-team x-pack/plugins/drilldowns/url_drilldown @elastic/appex-sharedux src/plugins/url_forwarding @elastic/kibana-visualizations packages/kbn-url-state @elastic/security-threat-hunting-investigations src/plugins/usage_collection @elastic/kibana-core test/plugin_functional/plugins/usage_collection @elastic/kibana-core -packages/kbn-use-tracked-promise @elastic/infra-monitoring-ui +packages/kbn-use-tracked-promise @elastic/obs-ux-logs-team packages/kbn-user-profile-components @elastic/kibana-security examples/user_profile_examples @elastic/kibana-security x-pack/test/security_api_integration/plugins/user_profiles_consumer @elastic/kibana-security packages/kbn-utility-types @elastic/kibana-core packages/kbn-utility-types-jest @elastic/kibana-operations packages/kbn-utils @elastic/kibana-operations -x-pack/plugins/ux @elastic/uptime +x-pack/plugins/ux @elastic/obs-ux-infra_services-team examples/v8_profiler_examples @elastic/response-ops packages/kbn-validate-next-docs-cli @elastic/kibana-operations src/plugins/vis_default_editor @elastic/kibana-visualizations @@ -822,7 +824,7 @@ src/plugins/visualizations @elastic/kibana-visualizations x-pack/plugins/watcher @elastic/platform-deployment-management packages/kbn-web-worker-stub @elastic/kibana-operations packages/kbn-whereis-pkg-cli @elastic/kibana-operations -packages/kbn-xstate-utils @elastic/infra-monitoring-ui +packages/kbn-xstate-utils @elastic/obs-ux-logs-team packages/kbn-yarn-lock-validator @elastic/kibana-operations packages/kbn-zod-helpers @elastic/security-detection-rule-management #### @@ -926,25 +928,79 @@ packages/kbn-monaco/src/esql @elastic/kibana-visualizations ### Observability Plugins -# Actionable observability -x-pack/packages/observability/alert_details @elastic/actionable-observability -x-pack/test/observability_functional @elastic/actionable-observability -x-pack/plugins/infra/public/alerting @elastic/actionable-observability -x-pack/plugins/infra/server/lib/alerting @elastic/actionable-observability - # Observability robots /.github/workflows/deploy-my-kibana.yml @elastic/observablt-robots /.github/workflows/oblt-github-commands @elastic/observablt-robots # Infra Monitoring -/x-pack/test/functional/apps/infra @elastic/infra-monitoring-ui -/x-pack/test/api_integration/apis/infra @elastic/infra-monitoring-ui +/x-pack/plugins/infra/server/routes @elastic/obs-ux-infra_services-team +/x-pack/plugins/infra/server/routes/log_analysis @elastic/obs-ux-logs-team +/x-pack/plugins/infra/server/routes/log_alerts @elastic/obs-ux-logs-team +/x-pack/plugins/infra/server/saved_objects/metrics_explorer_view @elastic/obs-ux-infra_services-team +/x-pack/plugins/infra/server/saved_objects/inventory_view @elastic/obs-ux-infra_services-team +/x-pack/plugins/infra/server/services @elastic/obs-ux-infra_services-team +/x-pack/plugins/infra/server/services/rules @elastic/obs-ux-infra_services-team @elastic/obs-ux-logs-team +/x-pack/plugins/infra/server/lib @elastic/obs-ux-infra_services-team +/x-pack/plugins/infra/server/lib/log_analysis @elastic/obs-ux-logs-team +/x-pack/plugins/infra/docs/state_machines @elastic/obs-ux-logs-team +/x-pack/plugins/infra/common/inventory_models @elastic/obs-ux-infra_services-team +/x-pack/plugins/infra/common/http_api/metrics_api.ts @elastic/obs-ux-infra_services-team +/x-pack/plugins/infra/common/http_api/snapshot_api.ts @elastic/obs-ux-infra_services-team +/x-pack/plugins/infra/common/http_api/log_analysis @elastic/obs-ux-logs-team +/x-pack/plugins/infra/common/http_api/metrics_explorer_views @elastic/obs-ux-infra_services-team +/x-pack/plugins/infra/common/http_api/host_details @elastic/obs-ux-infra_services-team +/x-pack/plugins/infra/common/http_api/log_alerts @elastic/obs-ux-logs-team +/x-pack/plugins/infra/common/snapshot_metric_i18n.ts @elastic/obs-ux-infra_services-team +/x-pack/plugins/infra/common/inventory_views @elastic/obs-ux-infra_services-team +/x-pack/plugins/infra/common/color_palette.test.ts @elastic/obs-ux-infra_services-team +/x-pack/plugins/infra/common/performance_tracing.ts @elastic/obs-ux-logs-team +/x-pack/plugins/infra/common/log_search_summary @elastic/obs-ux-logs-team +/x-pack/plugins/infra/common/metrics_sources @elastic/obs-ux-infra_services-team +/x-pack/plugins/infra/common/saved_views @elastic/obs-ux-infra_services-team +/x-pack/plugins/infra/common/infra_ml @elastic/obs-ux-infra_services-team +/x-pack/plugins/infra/common/formatters @elastic/obs-ux-infra_services-team +/x-pack/plugins/infra/common/log_text_scale @elastic/obs-ux-logs-team +/x-pack/plugins/infra/common/log_analysis @elastic/obs-ux-logs-team +/x-pack/plugins/infra/common/search_strategies/log_entries @elastic/obs-ux-logs-team +/x-pack/plugins/infra/common/metrics_explorer_views @elastic/obs-ux-infra_services-team +/x-pack/plugins/infra/common/source_configuration @elastic/obs-ux-infra_services-team +/x-pack/plugins/infra/common/color_palette.ts @elastic/obs-ux-infra_services-team +/x-pack/plugins/infra/common/log_search_result @elastic/obs-ux-logs-team +/x-pack/plugins/infra/public/apps/logs_app.tsx @elastic/obs-ux-logs-team +/x-pack/plugins/infra/public/apps/metrics_app.tsx @elastic/obs-ux-infra_services-team +/x-pack/plugins/infra/public/components/lens @elastic/obs-ux-infra_services-team +/x-pack/plugins/infra/public/components/try_it_button.tsx @elastic/obs-ux-infra_services-team +/x-pack/plugins/infra/public/components/fixed_datepicker.tsx +/x-pack/plugins/infra/public/components/logging @elastic/obs-ux-logs-team +/x-pack/plugins/infra/public/components/infrastructure_node_metrics_tables @elastic/obs-ux-infra_services-team +/x-pack/plugins/infra/public/components/saved_views @elastic/obs-ux-infra_services-team +/x-pack/plugins/infra/public/components/feature_feedback_button.tsx @elastic/obs-ux-infra_services-team +/x-pack/plugins/infra/public/components/log_stream @elastic/obs-ux-logs-team +/x-pack/plugins/infra/public/components/source_configuration @elastic/obs-ux-infra_services-team +/x-pack/plugins/infra/public/components/asset_details @elastic/obs-ux-infra_services-team +/x-pack/plugins/infra/public/containers/logs @elastic/obs-ux-logs-team +/x-pack/plugins/infra/public/containers/metrics_source @elastic/obs-ux-infra_services-team +/x-pack/plugins/infra/public/containers/metrics_explorer @elastic/obs-ux-infra_services-team +/x-pack/plugins/infra/public/containers/ml @elastic/obs-ux-infra_services-team +/x-pack/plugins/infra/public/pages/logs @elastic/obs-ux-logs-team +/x-pack/plugins/infra/public/pages/metrics @elastic/obs-ux-infra_services-team +/x-pack/plugins/infra/public/common @elastic/obs-ux-infra_services-team +/x-pack/plugins/infra/public/observability_logs @elastic/obs-ux-logs-team +/x-pack/plugins/infra/public/services @elastic/obs-ux-infra_services-team +/x-pack/test/functional/apps/infra @elastic/infra-monitoring-ui @elastic/obs-ux-infra_services-team @elastic/obs-ux-logs-team +/x-pack/test/api_integration/apis/infra @elastic/infra-monitoring-ui @elastic/obs-ux-infra_services-team + +# Actionable observability +x-pack/packages/observability/alert_details @elastic/obs-ux-management-team +x-pack/test/observability_functional @elastic/obs-ux-management-team +x-pack/plugins/infra/public/alerting @elastic/obs-ux-management-team +x-pack/plugins/infra/server/lib/alerting @elastic/obs-ux-management-team # Elastic Stack Monitoring -/x-pack/test/functional/apps/monitoring @elastic/infra-monitoring-ui -/x-pack/test/api_integration/apis/monitoring @elastic/infra-monitoring-ui -/x-pack/test/api_integration/apis/monitoring_collection @elastic/infra-monitoring-ui -/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer @elastic/infra-monitoring-ui +/x-pack/test/functional/apps/monitoring @elastic/obs-ux-infra_services-team +/x-pack/test/api_integration/apis/monitoring @elastic/obs-ux-infra_services-team +/x-pack/test/api_integration/apis/monitoring_collection @elastic/obs-ux-infra_services-team +/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer @elastic/obs-ux-logs-team # Fleet /fleet_packages.json @elastic/fleet @@ -955,29 +1011,27 @@ x-pack/plugins/infra/server/lib/alerting @elastic/actionable-observability /x-pack/plugins/fleet/server/services/elastic_agent_manifest.ts @elastic/fleet @elastic/obs-cloudnative-monitoring # APM -/x-pack/test/functional/apps/apm/ @elastic/apm-ui -/x-pack/test/apm_api_integration/ @elastic/apm-ui +/x-pack/test/functional/apps/apm/ @elastic/obs-ux-infra_services-team +/x-pack/test/apm_api_integration/ @elastic/obs-ux-infra_services-team /src/apm.js @elastic/kibana-core @vigneshshanmugam -/src/core/types/elasticsearch @elastic/apm-ui /packages/kbn-utility-types/src/dot.ts @dgieselaar /packages/kbn-utility-types/src/dot_test.ts @dgieselaar #CC# /src/plugins/apm_oss/ @elastic/apm-ui #CC# /x-pack/plugins/observability/ @elastic/apm-ui # Uptime -/x-pack/test/functional_with_es_ssl/apps/discover_ml_uptime/uptime/ @elastic/uptime -/x-pack/test/functional/apps/uptime @elastic/uptime -/x-pack/test/functional/es_archives/uptime @elastic/uptime -/x-pack/test/functional/services/uptime @elastic/uptime -/x-pack/test/api_integration/apis/uptime @elastic/uptime -/x-pack/test/api_integration/apis/synthetics @elastic/uptime -/x-pack/test/alerting_api_integration/observability/synthetics_rule.ts @elastic/uptime -/x-pack/test/alerting_api_integration/observability/index.ts @elastic/uptime - -# Client Side Monitoring / Uptime (lives in APM directories but owned by Uptime) -/x-pack/plugins/apm/public/application/uxApp.tsx @elastic/uptime -/x-pack/plugins/apm/public/components/app/rum_dashboard @elastic/uptime -/x-pack/test/apm_api_integration/tests/csm/ @elastic/uptime +/x-pack/test/functional_with_es_ssl/apps/discover_ml_uptime/uptime/ @elastic/obs-ux-infra_services-team +/x-pack/test/functional/apps/uptime @elastic/obs-ux-infra_services-team +/x-pack/test/functional/es_archives/uptime @elastic/obs-ux-infra_services-team +/x-pack/test/functional/services/uptime @elastic/obs-ux-infra_services-team +/x-pack/test/api_integration/apis/uptime @elastic/obs-ux-infra_services-team +/x-pack/test/api_integration/apis/synthetics @elastic/obs-ux-infra_services-team +/x-pack/test/alerting_api_integration/observability/synthetics_rule.ts @elastic/obs-ux-infra_services-team +/x-pack/test/alerting_api_integration/observability/index.ts @elastic/obs-ux-management-team + +# Logs +/x-pack/test/api_integration/apis/logs_ui @elastic/obs-ux-logs-team +/x-pack/test/functional/apps/observability_log_explorer @elastic/obs-ux-logs-team # Observability onboarding tour /x-pack/plugins/observability_shared/public/components/tour @elastic/platform-onboarding @@ -1118,7 +1172,7 @@ x-pack/plugins/cloud_integrations/cloud_full_story/server/config.ts @elastic/kib # Response Ops team /x-pack/test/alerting_api_integration/ @elastic/response-ops -/x-pack/test/alerting_api_integration/observability @elastic/actionable-observability +/x-pack/test/alerting_api_integration/observability @elastic/obs-ux-management-team /x-pack/test/plugin_api_integration/test_suites/task_manager/ @elastic/response-ops /x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/ @elastic/response-ops /docs/user/alerting/ @elastic/response-ops @@ -1246,6 +1300,7 @@ x-pack/plugins/cloud_integrations/cloud_full_story/server/config.ts @elastic/kib /x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_details @elastic/security-detection-rule-management /x-pack/plugins/security_solution/docs/testing/test_plans/detection_response/prebuilt_rules @elastic/security-detection-rule-management /x-pack/plugins/security_solution/docs/testing/test_plans/detection_response/rule_management @elastic/security-detection-rule-management +/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules @elastic/security-detection-rule-management /x-pack/plugins/security_solution/public/common/components/health_truncate_text @elastic/security-detection-rule-management /x-pack/plugins/security_solution/public/common/components/links_to_docs @elastic/security-detection-rule-management @@ -1365,6 +1420,7 @@ x-pack/plugins/security_solution/server/lib/telemetry/ @elastic/security-data-an ## Security Solution sub teams - security-engineering-productivity /x-pack/test/security_solution_cypress/* @elastic/security-engineering-productivity /x-pack/test/security_solution_cypress/cypress/* @elastic/security-engineering-productivity +/x-pack/test/security_solution_cypress/cypress/tasks/login.ts @elastic/security-engineering-productivity /x-pack/test/security_solution_cypress/es_archives @elastic/security-engineering-productivity /x-pack/plugins/security_solution/scripts/run_cypress @MadameSheema @patrykkopycinski @oatkiller @maximpn @banderror @@ -1383,9 +1439,9 @@ x-pack/plugins/security_solution/public/entity_analytics @elastic/security-entit x-pack/plugins/security_solution/public/explore/components/risk_score @elastic/security-entity-analytics x-pack/plugins/security_solution/public/overview/pages/entity_analytics.tsx @elastic/security-entity-analytics x-pack/plugins/security_solution/public/overview/components/entity_analytics -x-pack/plugins/security_solution/server/lib/risk_engine @elastic/security-entity-analytics +x-pack/plugins/security_solution/server/lib/entity_analytics @elastic/security-entity-analytics x-pack/plugins/security_solution/server/lib/risk_score @elastic/security-entity-analytics -x-pack/test/detection_engine_api_integration/security_and_spaces/group10/risk_engine @elastic/security-entity-analytics +x-pack/test/security_solution_api_integration/test_suites/entity_analytics @elastic/security-entity-analytics # Security Defend Workflows - OSQuery Ownership /x-pack/plugins/security_solution/common/api/detection_engine/model/rule_response_actions @elastic/security-defend-workflows @@ -1440,10 +1496,10 @@ x-pack/test/detection_engine_api_integration/security_and_spaces/group10/risk_en x-pack/plugins/translations/translations # Profiling api integration testing -x-pack/test/profiling_api_integration @elastic/profiling-ui +x-pack/test/profiling_api_integration @elastic/obs-ux-infra_services-team # Observability shared profiling -x-pack/plugins/observability_shared/public/components/profiling @elastic/profiling-ui +x-pack/plugins/observability_shared/public/components/profiling @elastic/obs-ux-infra_services-team # Shared UX packages/react @elastic/appex-sharedux diff --git a/api_docs/actions.mdx b/api_docs/actions.mdx index fa6f0b70f69ec8..cfe971c8e01be9 100644 --- a/api_docs/actions.mdx +++ b/api_docs/actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/actions title: "actions" image: https://source.unsplash.com/400x175/?github description: API docs for the actions plugin -date: 2023-11-06 +date: 2023-11-10 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 9a4a940ab39737..7c724461a9e3ef 100644 --- a/api_docs/advanced_settings.mdx +++ b/api_docs/advanced_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/advancedSettings title: "advancedSettings" image: https://source.unsplash.com/400x175/?github description: API docs for the advancedSettings plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'advancedSettings'] --- import advancedSettingsObj from './advanced_settings.devdocs.json'; diff --git a/api_docs/aiops.mdx b/api_docs/aiops.mdx index 783fbfb7a31f94..354011bbf232e8 100644 --- a/api_docs/aiops.mdx +++ b/api_docs/aiops.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/aiops title: "aiops" image: https://source.unsplash.com/400x175/?github description: API docs for the aiops plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'aiops'] --- import aiopsObj from './aiops.devdocs.json'; diff --git a/api_docs/alerting.mdx b/api_docs/alerting.mdx index 0a9419df631756..46fcda0e2c7ced 100644 --- a/api_docs/alerting.mdx +++ b/api_docs/alerting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/alerting title: "alerting" image: https://source.unsplash.com/400x175/?github description: API docs for the alerting plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'alerting'] --- import alertingObj from './alerting.devdocs.json'; diff --git a/api_docs/apm.devdocs.json b/api_docs/apm.devdocs.json index 2878d3c994d49e..c35b5362dce3df 100644 --- a/api_docs/apm.devdocs.json +++ b/api_docs/apm.devdocs.json @@ -124,7 +124,17 @@ "section": "def-common.LocatorPublic", "text": "LocatorPublic" }, - "<{ serviceName: undefined; } | ({ serviceName: string; } & { serviceOverviewTab?: \"errors\" | \"metrics\" | \"traces\" | \"logs\" | undefined; } & { query: { environment: \"ENVIRONMENT_NOT_DEFINED\" | \"ENVIRONMENT_ALL\" | ", + "<{ serviceName: undefined; } | ({ serviceName: string; } & { dashboardId: string; } & { query: { environment: \"ENVIRONMENT_NOT_DEFINED\" | \"ENVIRONMENT_ALL\" | ", + "Branded", + "; }; }) | ({ serviceName: string; } & { dashboardId?: undefined; } & { serviceOverviewTab?: \"errors\" | \"metrics\" | \"traces\" | \"logs\" | undefined; } & { query: { environment: \"ENVIRONMENT_NOT_DEFINED\" | \"ENVIRONMENT_ALL\" | ", "Branded", "" ], @@ -244,7 +244,7 @@ "children": [ { "parentPluginId": "dashboard", - "id": "def-public.getEmbeddableParams.$1", + "id": "def-public.getDashboardLocatorParamsFromEmbeddable.$1", "type": "Object", "tags": [], "label": "source", @@ -274,7 +274,7 @@ }, { "parentPluginId": "dashboard", - "id": "def-public.getEmbeddableParams.$2", + "id": "def-public.getDashboardLocatorParamsFromEmbeddable.$2", "type": "Object", "tags": [], "label": "options", @@ -685,8 +685,8 @@ "pluginId": "dashboard", "scope": "public", "docId": "kibDashboardPluginApi", - "section": "def-public.DashboardAppLocatorParams", - "text": "DashboardAppLocatorParams" + "section": "def-public.DashboardLocatorParams", + "text": "DashboardLocatorParams" }, ">" ], @@ -697,10 +697,10 @@ }, { "parentPluginId": "dashboard", - "id": "def-public.DashboardAppLocatorParams", + "id": "def-public.DashboardLocatorParams", "type": "Type", "tags": [], - "label": "DashboardAppLocatorParams", + "label": "DashboardLocatorParams", "description": [], "signature": [ "Partial" ], diff --git a/api_docs/dashboard_enhanced.mdx b/api_docs/dashboard_enhanced.mdx index 16bf8b156ab9e1..669ff705a81de2 100644 --- a/api_docs/dashboard_enhanced.mdx +++ b/api_docs/dashboard_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dashboardEnhanced title: "dashboardEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the dashboardEnhanced plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dashboardEnhanced'] --- import dashboardEnhancedObj from './dashboard_enhanced.devdocs.json'; diff --git a/api_docs/data.mdx b/api_docs/data.mdx index 6d4e212030ff5c..67837e8e89befb 100644 --- a/api_docs/data.mdx +++ b/api_docs/data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data title: "data" image: https://source.unsplash.com/400x175/?github description: API docs for the data plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data'] --- import dataObj from './data.devdocs.json'; diff --git a/api_docs/data_query.mdx b/api_docs/data_query.mdx index e64482bd4f09a0..6d3f601824faeb 100644 --- a/api_docs/data_query.mdx +++ b/api_docs/data_query.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data-query title: "data.query" image: https://source.unsplash.com/400x175/?github description: API docs for the data.query plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.query'] --- import dataQueryObj from './data_query.devdocs.json'; diff --git a/api_docs/data_search.mdx b/api_docs/data_search.mdx index 33e8535c35b2f6..44d105910a2310 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: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.search'] --- import dataSearchObj from './data_search.devdocs.json'; diff --git a/api_docs/data_view_editor.mdx b/api_docs/data_view_editor.mdx index 61097b12f73af9..ad68e4efd36b6f 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: 2023-11-06 +date: 2023-11-10 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 9010ea804d7fb4..78443ee9661b8a 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: 2023-11-06 +date: 2023-11-10 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 aff472aab574b8..ef6968f88252ee 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: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewManagement'] --- import dataViewManagementObj from './data_view_management.devdocs.json'; diff --git a/api_docs/data_views.mdx b/api_docs/data_views.mdx index 6339f692f0fd80..b7ab9bebaefdc8 100644 --- a/api_docs/data_views.mdx +++ b/api_docs/data_views.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViews title: "dataViews" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViews plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViews'] --- import dataViewsObj from './data_views.devdocs.json'; diff --git a/api_docs/data_visualizer.mdx b/api_docs/data_visualizer.mdx index e34fe763994e1f..a99330414e8107 100644 --- a/api_docs/data_visualizer.mdx +++ b/api_docs/data_visualizer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataVisualizer title: "dataVisualizer" image: https://source.unsplash.com/400x175/?github description: API docs for the dataVisualizer plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataVisualizer'] --- import dataVisualizerObj from './data_visualizer.devdocs.json'; diff --git a/api_docs/deprecations_by_api.mdx b/api_docs/deprecations_by_api.mdx index 8c0bd1f7aeda39..540826fdd6105f 100644 --- a/api_docs/deprecations_by_api.mdx +++ b/api_docs/deprecations_by_api.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsByApi slug: /kibana-dev-docs/api-meta/deprecated-api-list-by-api title: Deprecated API usage by API description: A list of deprecated APIs, which plugins are still referencing them, and when they need to be removed by. -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- diff --git a/api_docs/deprecations_by_plugin.mdx b/api_docs/deprecations_by_plugin.mdx index 4c718d7d2ebf4b..040f6fbead292e 100644 --- a/api_docs/deprecations_by_plugin.mdx +++ b/api_docs/deprecations_by_plugin.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsByPlugin slug: /kibana-dev-docs/api-meta/deprecated-api-list-by-plugin title: Deprecated API usage by plugin description: A list of deprecated APIs, which plugins are still referencing them, and when they need to be removed by. -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- diff --git a/api_docs/deprecations_by_team.mdx b/api_docs/deprecations_by_team.mdx index 577b7e77db3711..763c598f167b70 100644 --- a/api_docs/deprecations_by_team.mdx +++ b/api_docs/deprecations_by_team.mdx @@ -7,22 +7,11 @@ id: kibDevDocsDeprecationsDueByTeam slug: /kibana-dev-docs/api-meta/deprecations-due-by-team title: Deprecated APIs due to be removed, by team description: Lists the teams that are referencing deprecated APIs with a remove by date. -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- -## @elastic/apm-ui - -| Plugin | Deprecated API | Reference location(s) | Remove By | -| --------|-------|-----------|-----------| -| apm | | [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/apm/public/plugin.ts#:~:text=environment) | 8.8.0 | -| apm | | [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/apm/common/license_check.test.ts#:~:text=mode), [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/apm/common/license_check.test.ts#:~:text=mode), [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/apm/common/license_check.test.ts#:~:text=mode), [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/apm/common/license_check.test.ts#:~:text=mode), [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/apm/common/license_check.test.ts#:~:text=mode), [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/apm/common/license_check.test.ts#:~:text=mode), [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/apm/common/license_check.test.ts#:~:text=mode), [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/apm/common/license_check.test.ts#:~:text=mode), [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/apm/common/license_check.test.ts#:~:text=mode), [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/apm/common/license_check.test.ts#:~:text=mode)+ 2 more | 8.8.0 | -| apm | | [license_context.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/apm/public/context/license/license_context.tsx#:~:text=license%24) | 8.8.0 | -| apm | | [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/apm/common/license_check.test.ts#:~:text=mode), [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/apm/common/license_check.test.ts#:~:text=mode), [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/apm/common/license_check.test.ts#:~:text=mode), [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/apm/common/license_check.test.ts#:~:text=mode), [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/apm/common/license_check.test.ts#:~:text=mode), [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/apm/common/license_check.test.ts#:~:text=mode), [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/apm/common/license_check.test.ts#:~:text=mode), [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/apm/common/license_check.test.ts#:~:text=mode), [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/apm/common/license_check.test.ts#:~:text=mode), [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/apm/common/license_check.test.ts#:~:text=mode)+ 2 more | 8.8.0 | - - - ## @elastic/appex-sharedux | Plugin | Deprecated API | Reference location(s) | Remove By | @@ -124,21 +113,24 @@ migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/ -## @elastic/platform-deployment-management +## @elastic/obs-ux-infra_services-team | Plugin | Deprecated API | Reference location(s) | Remove By | | --------|-------|-----------|-----------| -| indexLifecycleManagement | | [license.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/index_lifecycle_management/server/services/license.ts#:~:text=license%24), [reindex_service.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.ts#:~:text=license%24), [reindex_service.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.test.ts#:~:text=license%24), [reindex_service.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.test.ts#:~:text=license%24), [license.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/painless_lab/server/services/license.ts#:~:text=license%24), [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/remote_clusters/server/plugin.ts#:~:text=license%24), [license.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/rollup/server/services/license.ts#:~:text=license%24), [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/searchprofiler/server/plugin.ts#:~:text=license%24), [license.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/snapshot_restore/server/services/license.ts#:~:text=license%24) | 8.8.0 | -| management | | [application.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/management/public/application.tsx#:~:text=appBasePath) | 8.8.0 | -| licenseManagement | | [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/license_management/public/plugin.ts#:~:text=license%24), [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cross_cluster_replication/public/plugin.ts#:~:text=license%24), [plugin.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/painless_lab/public/plugin.tsx#:~:text=license%24), [plugin.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/painless_lab/public/plugin.tsx#:~:text=license%24), [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/searchprofiler/public/plugin.ts#:~:text=license%24), [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/searchprofiler/public/plugin.ts#:~:text=license%24), [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/watcher/public/plugin.ts#:~:text=license%24), [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/watcher/public/plugin.ts#:~:text=license%24) | 8.8.0 | +| apm | | [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/apm/public/plugin.ts#:~:text=environment) | 8.8.0 | +| apm | | [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/apm/common/license_check.test.ts#:~:text=mode), [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/apm/common/license_check.test.ts#:~:text=mode), [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/apm/common/license_check.test.ts#:~:text=mode), [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/apm/common/license_check.test.ts#:~:text=mode), [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/apm/common/license_check.test.ts#:~:text=mode), [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/apm/common/license_check.test.ts#:~:text=mode), [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/apm/common/license_check.test.ts#:~:text=mode), [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/apm/common/license_check.test.ts#:~:text=mode), [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/apm/common/license_check.test.ts#:~:text=mode), [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/apm/common/license_check.test.ts#:~:text=mode)+ 2 more | 8.8.0 | +| apm | | [license_context.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/apm/public/context/license/license_context.tsx#:~:text=license%24), [license_context.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/profiling/public/components/contexts/license/license_context.tsx#:~:text=license%24) | 8.8.0 | +| apm | | [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/apm/common/license_check.test.ts#:~:text=mode), [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/apm/common/license_check.test.ts#:~:text=mode), [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/apm/common/license_check.test.ts#:~:text=mode), [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/apm/common/license_check.test.ts#:~:text=mode), [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/apm/common/license_check.test.ts#:~:text=mode), [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/apm/common/license_check.test.ts#:~:text=mode), [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/apm/common/license_check.test.ts#:~:text=mode), [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/apm/common/license_check.test.ts#:~:text=mode), [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/apm/common/license_check.test.ts#:~:text=mode), [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/apm/common/license_check.test.ts#:~:text=mode)+ 2 more | 8.8.0 | -## @elastic/profiling-ui +## @elastic/platform-deployment-management | Plugin | Deprecated API | Reference location(s) | Remove By | | --------|-------|-----------|-----------| -| profiling | | [license_context.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/profiling/public/components/contexts/license/license_context.tsx#:~:text=license%24) | 8.8.0 | +| indexLifecycleManagement | | [license.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/index_lifecycle_management/server/services/license.ts#:~:text=license%24), [reindex_service.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.ts#:~:text=license%24), [reindex_service.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.test.ts#:~:text=license%24), [reindex_service.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.test.ts#:~:text=license%24), [license.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/painless_lab/server/services/license.ts#:~:text=license%24), [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/remote_clusters/server/plugin.ts#:~:text=license%24), [license.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/rollup/server/services/license.ts#:~:text=license%24), [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/searchprofiler/server/plugin.ts#:~:text=license%24), [license.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/snapshot_restore/server/services/license.ts#:~:text=license%24) | 8.8.0 | +| management | | [application.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/management/public/application.tsx#:~:text=appBasePath) | 8.8.0 | +| licenseManagement | | [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/license_management/public/plugin.ts#:~:text=license%24), [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cross_cluster_replication/public/plugin.ts#:~:text=license%24), [plugin.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/painless_lab/public/plugin.tsx#:~:text=license%24), [plugin.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/painless_lab/public/plugin.tsx#:~:text=license%24), [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/searchprofiler/public/plugin.ts#:~:text=license%24), [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/searchprofiler/public/plugin.ts#:~:text=license%24), [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/watcher/public/plugin.ts#:~:text=license%24), [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/watcher/public/plugin.ts#:~:text=license%24) | 8.8.0 | diff --git a/api_docs/dev_tools.mdx b/api_docs/dev_tools.mdx index 02b9efb3d3f554..fcd8246f503527 100644 --- a/api_docs/dev_tools.mdx +++ b/api_docs/dev_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/devTools title: "devTools" image: https://source.unsplash.com/400x175/?github description: API docs for the devTools plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'devTools'] --- import devToolsObj from './dev_tools.devdocs.json'; diff --git a/api_docs/discover.mdx b/api_docs/discover.mdx index 7252758b626118..7399ac8d106090 100644 --- a/api_docs/discover.mdx +++ b/api_docs/discover.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/discover title: "discover" image: https://source.unsplash.com/400x175/?github description: API docs for the discover plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discover'] --- import discoverObj from './discover.devdocs.json'; diff --git a/api_docs/discover_enhanced.mdx b/api_docs/discover_enhanced.mdx index 91447e765fb5f5..2560ceff5721a2 100644 --- a/api_docs/discover_enhanced.mdx +++ b/api_docs/discover_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/discoverEnhanced title: "discoverEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the discoverEnhanced plugin -date: 2023-11-06 +date: 2023-11-10 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 707335457ed5fc..4406ce8e354312 100644 --- a/api_docs/ecs_data_quality_dashboard.mdx +++ b/api_docs/ecs_data_quality_dashboard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ecsDataQualityDashboard title: "ecsDataQualityDashboard" image: https://source.unsplash.com/400x175/?github description: API docs for the ecsDataQualityDashboard plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ecsDataQualityDashboard'] --- import ecsDataQualityDashboardObj from './ecs_data_quality_dashboard.devdocs.json'; diff --git a/api_docs/elastic_assistant.mdx b/api_docs/elastic_assistant.mdx index 32fbc7c59a2c58..389118ca73ba87 100644 --- a/api_docs/elastic_assistant.mdx +++ b/api_docs/elastic_assistant.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/elasticAssistant title: "elasticAssistant" image: https://source.unsplash.com/400x175/?github description: API docs for the elasticAssistant plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'elasticAssistant'] --- import elasticAssistantObj from './elastic_assistant.devdocs.json'; diff --git a/api_docs/embeddable.mdx b/api_docs/embeddable.mdx index 08416bccde75cb..69ff45a1b76f4f 100644 --- a/api_docs/embeddable.mdx +++ b/api_docs/embeddable.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/embeddable title: "embeddable" image: https://source.unsplash.com/400x175/?github description: API docs for the embeddable plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'embeddable'] --- import embeddableObj from './embeddable.devdocs.json'; diff --git a/api_docs/embeddable_enhanced.mdx b/api_docs/embeddable_enhanced.mdx index 424d5405407d29..3814474a19718e 100644 --- a/api_docs/embeddable_enhanced.mdx +++ b/api_docs/embeddable_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/embeddableEnhanced title: "embeddableEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the embeddableEnhanced plugin -date: 2023-11-06 +date: 2023-11-10 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 5d8c108d75bb43..683f865c62335f 100644 --- a/api_docs/encrypted_saved_objects.mdx +++ b/api_docs/encrypted_saved_objects.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/encryptedSavedObjects title: "encryptedSavedObjects" image: https://source.unsplash.com/400x175/?github description: API docs for the encryptedSavedObjects plugin -date: 2023-11-06 +date: 2023-11-10 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 c50f1c19eadabf..d4ead5258f45c5 100644 --- a/api_docs/enterprise_search.mdx +++ b/api_docs/enterprise_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/enterpriseSearch title: "enterpriseSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the enterpriseSearch plugin -date: 2023-11-06 +date: 2023-11-10 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 b8bf9d49359ec8..b778008638278e 100644 --- a/api_docs/es_ui_shared.mdx +++ b/api_docs/es_ui_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/esUiShared title: "esUiShared" image: https://source.unsplash.com/400x175/?github description: API docs for the esUiShared plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'esUiShared'] --- import esUiSharedObj from './es_ui_shared.devdocs.json'; diff --git a/api_docs/event_annotation.mdx b/api_docs/event_annotation.mdx index a105a56a4c5f15..cb2c5f6afd86b7 100644 --- a/api_docs/event_annotation.mdx +++ b/api_docs/event_annotation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/eventAnnotation title: "eventAnnotation" image: https://source.unsplash.com/400x175/?github description: API docs for the eventAnnotation plugin -date: 2023-11-06 +date: 2023-11-10 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 7f0f0442722c94..67a44e81e21a6c 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: 2023-11-06 +date: 2023-11-10 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 5bf3b72044b7cf..215034cef5227b 100644 --- a/api_docs/event_log.mdx +++ b/api_docs/event_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/eventLog title: "eventLog" image: https://source.unsplash.com/400x175/?github description: API docs for the eventLog plugin -date: 2023-11-06 +date: 2023-11-10 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 6d1adff608fc5b..275cde683ddb8b 100644 --- a/api_docs/exploratory_view.mdx +++ b/api_docs/exploratory_view.mdx @@ -8,14 +8,14 @@ slug: /kibana-dev-docs/api/exploratoryView title: "exploratoryView" image: https://source.unsplash.com/400x175/?github description: API docs for the exploratoryView plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'exploratoryView'] --- import exploratoryViewObj from './exploratory_view.devdocs.json'; -Contact [@elastic/uptime](https://github.com/orgs/elastic/teams/uptime) for questions regarding this plugin. +Contact [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) for questions regarding this plugin. **Code health stats** diff --git a/api_docs/expression_error.mdx b/api_docs/expression_error.mdx index 706729aa1e0be2..4826fc43a0cb16 100644 --- a/api_docs/expression_error.mdx +++ b/api_docs/expression_error.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionError title: "expressionError" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionError plugin -date: 2023-11-06 +date: 2023-11-10 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 7bedc54897dad8..73f21468f7967b 100644 --- a/api_docs/expression_gauge.mdx +++ b/api_docs/expression_gauge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionGauge title: "expressionGauge" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionGauge plugin -date: 2023-11-06 +date: 2023-11-10 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 fda8ab295f2603..fbe776832cee1a 100644 --- a/api_docs/expression_heatmap.mdx +++ b/api_docs/expression_heatmap.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionHeatmap title: "expressionHeatmap" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionHeatmap plugin -date: 2023-11-06 +date: 2023-11-10 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 7cbc0ff5432e8c..7df7e492abf662 100644 --- a/api_docs/expression_image.mdx +++ b/api_docs/expression_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionImage title: "expressionImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionImage plugin -date: 2023-11-06 +date: 2023-11-10 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 168d406227f63a..d65cad5852ba56 100644 --- a/api_docs/expression_legacy_metric_vis.mdx +++ b/api_docs/expression_legacy_metric_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionLegacyMetricVis title: "expressionLegacyMetricVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionLegacyMetricVis plugin -date: 2023-11-06 +date: 2023-11-10 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 563e42b05111d6..eb996bf5f6dbb0 100644 --- a/api_docs/expression_metric.mdx +++ b/api_docs/expression_metric.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionMetric title: "expressionMetric" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionMetric plugin -date: 2023-11-06 +date: 2023-11-10 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 9fc401e24f307b..b20e31573ce0f9 100644 --- a/api_docs/expression_metric_vis.mdx +++ b/api_docs/expression_metric_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionMetricVis title: "expressionMetricVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionMetricVis plugin -date: 2023-11-06 +date: 2023-11-10 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 08b3ed7c62f2dd..6a35064c2b9e0e 100644 --- a/api_docs/expression_partition_vis.mdx +++ b/api_docs/expression_partition_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionPartitionVis title: "expressionPartitionVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionPartitionVis plugin -date: 2023-11-06 +date: 2023-11-10 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 2026f1039a2596..b3bd8d94787b27 100644 --- a/api_docs/expression_repeat_image.mdx +++ b/api_docs/expression_repeat_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionRepeatImage title: "expressionRepeatImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionRepeatImage plugin -date: 2023-11-06 +date: 2023-11-10 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 0441e4071e63a8..bb7d4e54b665a5 100644 --- a/api_docs/expression_reveal_image.mdx +++ b/api_docs/expression_reveal_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionRevealImage title: "expressionRevealImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionRevealImage plugin -date: 2023-11-06 +date: 2023-11-10 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 6470b347b3c48b..ce33700ad9eaeb 100644 --- a/api_docs/expression_shape.mdx +++ b/api_docs/expression_shape.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionShape title: "expressionShape" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionShape plugin -date: 2023-11-06 +date: 2023-11-10 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 9529fb800373d5..2abc263bab12ad 100644 --- a/api_docs/expression_tagcloud.mdx +++ b/api_docs/expression_tagcloud.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionTagcloud title: "expressionTagcloud" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionTagcloud plugin -date: 2023-11-06 +date: 2023-11-10 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 8a980a575c25fa..3bfc54bff94211 100644 --- a/api_docs/expression_x_y.mdx +++ b/api_docs/expression_x_y.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionXY title: "expressionXY" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionXY plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionXY'] --- import expressionXYObj from './expression_x_y.devdocs.json'; diff --git a/api_docs/expressions.mdx b/api_docs/expressions.mdx index 9101b0ca949e62..c098590e8f3aa7 100644 --- a/api_docs/expressions.mdx +++ b/api_docs/expressions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressions title: "expressions" image: https://source.unsplash.com/400x175/?github description: API docs for the expressions plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressions'] --- import expressionsObj from './expressions.devdocs.json'; diff --git a/api_docs/features.mdx b/api_docs/features.mdx index 8575084e9822a2..940bf4813eb0fe 100644 --- a/api_docs/features.mdx +++ b/api_docs/features.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/features title: "features" image: https://source.unsplash.com/400x175/?github description: API docs for the features plugin -date: 2023-11-06 +date: 2023-11-10 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 f93667bc72e74a..1acfc950293465 100644 --- a/api_docs/field_formats.mdx +++ b/api_docs/field_formats.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fieldFormats title: "fieldFormats" image: https://source.unsplash.com/400x175/?github description: API docs for the fieldFormats plugin -date: 2023-11-06 +date: 2023-11-10 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 c42ddd31012bf9..bf46c5e775d3bf 100644 --- a/api_docs/file_upload.mdx +++ b/api_docs/file_upload.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fileUpload title: "fileUpload" image: https://source.unsplash.com/400x175/?github description: API docs for the fileUpload plugin -date: 2023-11-06 +date: 2023-11-10 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 aa07659af92c1e..cc2a4f021dc33d 100644 --- a/api_docs/files.mdx +++ b/api_docs/files.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/files title: "files" image: https://source.unsplash.com/400x175/?github description: API docs for the files plugin -date: 2023-11-06 +date: 2023-11-10 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 59b3abf319cf5d..e5e76deedd6698 100644 --- a/api_docs/files_management.mdx +++ b/api_docs/files_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/filesManagement title: "filesManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the filesManagement plugin -date: 2023-11-06 +date: 2023-11-10 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 2fae327702d1cf..70d72df7dda0dd 100644 --- a/api_docs/fleet.devdocs.json +++ b/api_docs/fleet.devdocs.json @@ -22507,6 +22507,20 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "fleet", + "id": "def-common.PackageSpecManifest.agent", + "type": "Object", + "tags": [], + "label": "agent", + "description": [], + "signature": [ + "{ privileges?: { root?: boolean | undefined; } | undefined; } | undefined" + ], + "path": "x-pack/plugins/fleet/common/types/models/package_spec.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "fleet", "id": "def-common.PackageSpecManifest.asset_tags", @@ -22863,6 +22877,36 @@ "path": "x-pack/plugins/fleet/common/types/models/epm.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "fleet", + "id": "def-common.RegistryDataStream.RegistryDataStreamKeys.lifecycle", + "type": "Object", + "tags": [], + "label": "[RegistryDataStreamKeys.lifecycle]", + "description": [], + "signature": [ + "RegistryDataStreamLifecycle", + " | undefined" + ], + "path": "x-pack/plugins/fleet/common/types/models/epm.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "fleet", + "id": "def-common.RegistryDataStream.RegistryDataStreamKeys.agent", + "type": "Object", + "tags": [], + "label": "[RegistryDataStreamKeys.agent]", + "description": [], + "signature": [ + "RegistryAgent", + " | undefined" + ], + "path": "x-pack/plugins/fleet/common/types/models/epm.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false diff --git a/api_docs/fleet.mdx b/api_docs/fleet.mdx index be0bce30ed9121..76ace056690af3 100644 --- a/api_docs/fleet.mdx +++ b/api_docs/fleet.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fleet title: "fleet" image: https://source.unsplash.com/400x175/?github description: API docs for the fleet plugin -date: 2023-11-06 +date: 2023-11-10 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 | |-------------------|-----------|------------------------|-----------------| -| 1209 | 3 | 1091 | 45 | +| 1212 | 3 | 1094 | 46 | ## Client diff --git a/api_docs/global_search.mdx b/api_docs/global_search.mdx index 58feafb9b8d5f5..8b01ecbd24e898 100644 --- a/api_docs/global_search.mdx +++ b/api_docs/global_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/globalSearch title: "globalSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the globalSearch plugin -date: 2023-11-06 +date: 2023-11-10 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 3c921000c98dc7..8a8529e5ac1d2f 100644 --- a/api_docs/guided_onboarding.mdx +++ b/api_docs/guided_onboarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/guidedOnboarding title: "guidedOnboarding" image: https://source.unsplash.com/400x175/?github description: API docs for the guidedOnboarding plugin -date: 2023-11-06 +date: 2023-11-10 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 1bfc0dd5b7f3fd..92a80f07a1cbfd 100644 --- a/api_docs/home.mdx +++ b/api_docs/home.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/home title: "home" image: https://source.unsplash.com/400x175/?github description: API docs for the home plugin -date: 2023-11-06 +date: 2023-11-10 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 2734c236ce8776..2b3334256a6a6b 100644 --- a/api_docs/image_embeddable.mdx +++ b/api_docs/image_embeddable.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/imageEmbeddable title: "imageEmbeddable" image: https://source.unsplash.com/400x175/?github description: API docs for the imageEmbeddable plugin -date: 2023-11-06 +date: 2023-11-10 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 744f652ec8a38b..bcf41cc2783601 100644 --- a/api_docs/index_lifecycle_management.mdx +++ b/api_docs/index_lifecycle_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/indexLifecycleManagement title: "indexLifecycleManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the indexLifecycleManagement plugin -date: 2023-11-06 +date: 2023-11-10 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 31a67dfc57094a..b5ba6df118a221 100644 --- a/api_docs/index_management.devdocs.json +++ b/api_docs/index_management.devdocs.json @@ -2585,6 +2585,20 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "indexManagement", + "id": "def-common.TemplateDeserialized.allowAutoCreate", + "type": "CompoundType", + "tags": [], + "label": "allowAutoCreate", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "x-pack/plugins/index_management/common/types/templates.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "indexManagement", "id": "def-common.TemplateDeserialized.order", @@ -2997,6 +3011,20 @@ "path": "x-pack/plugins/index_management/common/types/templates.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "indexManagement", + "id": "def-common.TemplateSerialized.allow_auto_create", + "type": "CompoundType", + "tags": [], + "label": "allow_auto_create", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "x-pack/plugins/index_management/common/types/templates.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false diff --git a/api_docs/index_management.mdx b/api_docs/index_management.mdx index 105073de1a0edb..663fa0cd352679 100644 --- a/api_docs/index_management.mdx +++ b/api_docs/index_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/indexManagement title: "indexManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the indexManagement plugin -date: 2023-11-06 +date: 2023-11-10 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 | |-------------------|-----------|------------------------|-----------------| -| 204 | 0 | 199 | 4 | +| 206 | 0 | 201 | 4 | ## Client diff --git a/api_docs/infra.devdocs.json b/api_docs/infra.devdocs.json index 7bd53f61bf8b71..6329b376e20cc7 100644 --- a/api_docs/infra.devdocs.json +++ b/api_docs/infra.devdocs.json @@ -400,55 +400,6 @@ "deprecated": false, "trackAdoption": false, "children": [ - { - "parentPluginId": "infra", - "id": "def-server.InfraPluginSetup.defineInternalSourceConfiguration", - "type": "Function", - "tags": [], - "label": "defineInternalSourceConfiguration", - "description": [], - "signature": [ - "(sourceId: string, sourceProperties: ", - "InfraStaticSourceConfiguration", - ") => void" - ], - "path": "x-pack/plugins/infra/server/types.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "infra", - "id": "def-server.InfraPluginSetup.defineInternalSourceConfiguration.$1", - "type": "string", - "tags": [], - "label": "sourceId", - "description": [], - "signature": [ - "string" - ], - "path": "x-pack/plugins/infra/server/types.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - }, - { - "parentPluginId": "infra", - "id": "def-server.InfraPluginSetup.defineInternalSourceConfiguration.$2", - "type": "Object", - "tags": [], - "label": "sourceProperties", - "description": [], - "signature": [ - "InfraStaticSourceConfiguration" - ], - "path": "x-pack/plugins/infra/server/types.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [] - }, { "parentPluginId": "infra", "id": "def-server.InfraPluginSetup.inventoryViews", diff --git a/api_docs/infra.mdx b/api_docs/infra.mdx index e2d6b0a17b2d32..8a9e88cf2db65a 100644 --- a/api_docs/infra.mdx +++ b/api_docs/infra.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/infra title: "infra" image: https://source.unsplash.com/400x175/?github description: API docs for the infra plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'infra'] --- import infraObj from './infra.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/infra-monitoring-ui](https://github.com/orgs/elastic/teams/inf | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 35 | 0 | 32 | 9 | +| 32 | 0 | 29 | 8 | ## Client diff --git a/api_docs/inspector.mdx b/api_docs/inspector.mdx index f5b95a12be7a64..0b1e5ae24f771b 100644 --- a/api_docs/inspector.mdx +++ b/api_docs/inspector.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/inspector title: "inspector" image: https://source.unsplash.com/400x175/?github description: API docs for the inspector plugin -date: 2023-11-06 +date: 2023-11-10 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 6799b154e370c7..4c43e51e2d2cef 100644 --- a/api_docs/interactive_setup.mdx +++ b/api_docs/interactive_setup.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/interactiveSetup title: "interactiveSetup" image: https://source.unsplash.com/400x175/?github description: API docs for the interactiveSetup plugin -date: 2023-11-06 +date: 2023-11-10 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 c177610fdd83c2..8febe841d8e0c0 100644 --- a/api_docs/kbn_ace.mdx +++ b/api_docs/kbn_ace.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ace title: "@kbn/ace" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ace plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ace'] --- import kbnAceObj from './kbn_ace.devdocs.json'; diff --git a/api_docs/kbn_aiops_components.mdx b/api_docs/kbn_aiops_components.mdx index 3e0ff4b68b7b7f..94e5c48c4496cb 100644 --- a/api_docs/kbn_aiops_components.mdx +++ b/api_docs/kbn_aiops_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-aiops-components title: "@kbn/aiops-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/aiops-components plugin -date: 2023-11-06 +date: 2023-11-10 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 3d2951d200b868..5349f3af5fc605 100644 --- a/api_docs/kbn_aiops_utils.mdx +++ b/api_docs/kbn_aiops_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-aiops-utils title: "@kbn/aiops-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/aiops-utils plugin -date: 2023-11-06 +date: 2023-11-10 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 d0de2139c489a2..39ef4801b139c1 100644 --- a/api_docs/kbn_alerting_api_integration_helpers.mdx +++ b/api_docs/kbn_alerting_api_integration_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerting-api-integration-helpers title: "@kbn/alerting-api-integration-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerting-api-integration-helpers plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerting-api-integration-helpers'] --- import kbnAlertingApiIntegrationHelpersObj from './kbn_alerting_api_integration_helpers.devdocs.json'; diff --git a/api_docs/kbn_alerting_state_types.mdx b/api_docs/kbn_alerting_state_types.mdx index 39ba0c1e81f565..decbf540164919 100644 --- a/api_docs/kbn_alerting_state_types.mdx +++ b/api_docs/kbn_alerting_state_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerting-state-types title: "@kbn/alerting-state-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerting-state-types plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerting-state-types'] --- import kbnAlertingStateTypesObj from './kbn_alerting_state_types.devdocs.json'; diff --git a/api_docs/kbn_alerts_as_data_utils.mdx b/api_docs/kbn_alerts_as_data_utils.mdx index 4bed011702fc59..a5d117d5723221 100644 --- a/api_docs/kbn_alerts_as_data_utils.mdx +++ b/api_docs/kbn_alerts_as_data_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerts-as-data-utils title: "@kbn/alerts-as-data-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerts-as-data-utils plugin -date: 2023-11-06 +date: 2023-11-10 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 7e32c52afcb276..ebbdbca8a9aaa1 100644 --- a/api_docs/kbn_alerts_ui_shared.mdx +++ b/api_docs/kbn_alerts_ui_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerts-ui-shared title: "@kbn/alerts-ui-shared" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerts-ui-shared plugin -date: 2023-11-06 +date: 2023-11-10 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 74b3efc22e7a8e..0b7dd80b3b3a97 100644 --- a/api_docs/kbn_analytics.mdx +++ b/api_docs/kbn_analytics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics title: "@kbn/analytics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics'] --- import kbnAnalyticsObj from './kbn_analytics.devdocs.json'; diff --git a/api_docs/kbn_analytics_client.devdocs.json b/api_docs/kbn_analytics_client.devdocs.json index b6c69aec07ca3a..49f322df02d0f1 100644 --- a/api_docs/kbn_analytics_client.devdocs.json +++ b/api_docs/kbn_analytics_client.devdocs.json @@ -796,15 +796,15 @@ }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/risk_engine/tasks/risk_scoring_task.ts" + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/tasks/risk_scoring_task.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/risk_engine/tasks/risk_scoring_task.ts" + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/tasks/risk_scoring_task.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/risk_engine/tasks/risk_scoring_task.ts" + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/tasks/risk_scoring_task.ts" }, { "plugin": "securitySolution", @@ -872,19 +872,19 @@ }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/risk_engine/tasks/risk_scoring_task.test.ts" + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/tasks/risk_scoring_task.test.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/risk_engine/tasks/risk_scoring_task.test.ts" + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/tasks/risk_scoring_task.test.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/risk_engine/tasks/risk_scoring_task.test.ts" + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/tasks/risk_scoring_task.test.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/risk_engine/tasks/risk_scoring_task.test.ts" + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/tasks/risk_scoring_task.test.ts" }, { "plugin": "@kbn/core-analytics-browser-mocks", diff --git a/api_docs/kbn_analytics_client.mdx b/api_docs/kbn_analytics_client.mdx index f0f34d0731023f..7683e2bcb0267e 100644 --- a/api_docs/kbn_analytics_client.mdx +++ b/api_docs/kbn_analytics_client.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-client title: "@kbn/analytics-client" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-client plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-client'] --- import kbnAnalyticsClientObj from './kbn_analytics_client.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx index 31ed3e86131847..09c2b17d706d91 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-browser title: "@kbn/analytics-shippers-elastic-v3-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-elastic-v3-browser plugin -date: 2023-11-06 +date: 2023-11-10 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 f4a9d738c96585..b4541ac4a4ad92 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-common title: "@kbn/analytics-shippers-elastic-v3-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-elastic-v3-common plugin -date: 2023-11-06 +date: 2023-11-10 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 1b060b7a102844..a40c3f42195964 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-server title: "@kbn/analytics-shippers-elastic-v3-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-elastic-v3-server plugin -date: 2023-11-06 +date: 2023-11-10 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 15fd05c72af467..ca5f113a75fab4 100644 --- a/api_docs/kbn_analytics_shippers_fullstory.mdx +++ b/api_docs/kbn_analytics_shippers_fullstory.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-fullstory title: "@kbn/analytics-shippers-fullstory" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-fullstory plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-fullstory'] --- import kbnAnalyticsShippersFullstoryObj from './kbn_analytics_shippers_fullstory.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_gainsight.mdx b/api_docs/kbn_analytics_shippers_gainsight.mdx index 6dbdf4a26208b5..2df958d49823af 100644 --- a/api_docs/kbn_analytics_shippers_gainsight.mdx +++ b/api_docs/kbn_analytics_shippers_gainsight.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-gainsight title: "@kbn/analytics-shippers-gainsight" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-gainsight plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-gainsight'] --- import kbnAnalyticsShippersGainsightObj from './kbn_analytics_shippers_gainsight.devdocs.json'; diff --git a/api_docs/kbn_apm_config_loader.mdx b/api_docs/kbn_apm_config_loader.mdx index 2eef15fe62da19..de4b1ddda4dd5e 100644 --- a/api_docs/kbn_apm_config_loader.mdx +++ b/api_docs/kbn_apm_config_loader.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-config-loader title: "@kbn/apm-config-loader" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-config-loader plugin -date: 2023-11-06 +date: 2023-11-10 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 421eaed8682b7d..b589198be82c35 100644 --- a/api_docs/kbn_apm_synthtrace.mdx +++ b/api_docs/kbn_apm_synthtrace.mdx @@ -8,14 +8,14 @@ slug: /kibana-dev-docs/api/kbn-apm-synthtrace title: "@kbn/apm-synthtrace" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-synthtrace plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-synthtrace'] --- import kbnApmSynthtraceObj from './kbn_apm_synthtrace.devdocs.json'; -Contact [@elastic/apm-ui](https://github.com/orgs/elastic/teams/apm-ui) for questions regarding this plugin. +Contact [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) for questions regarding this plugin. **Code health stats** diff --git a/api_docs/kbn_apm_synthtrace_client.devdocs.json b/api_docs/kbn_apm_synthtrace_client.devdocs.json index b3932f6a80802a..2bd574701728db 100644 --- a/api_docs/kbn_apm_synthtrace_client.devdocs.json +++ b/api_docs/kbn_apm_synthtrace_client.devdocs.json @@ -1181,6 +1181,23 @@ ], "returnComment": [] }, + { + "parentPluginId": "@kbn/apm-synthtrace-client", + "id": "def-common.MobileDevice.event", + "type": "Function", + "tags": [], + "label": "event", + "description": [], + "signature": [ + "() => ", + "Event" + ], + "path": "packages/kbn-apm-synthtrace-client/src/lib/apm/mobile_device.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, { "parentPluginId": "@kbn/apm-synthtrace-client", "id": "def-common.MobileDevice.transaction", @@ -2439,7 +2456,7 @@ "GeoLocation", "; 'client.geo.region_iso_code': string; 'client.geo.region_name': string; 'client.ip': string; 'cloud.account.id': string; 'cloud.account.name': string; 'cloud.availability_zone': string; 'cloud.machine.type': string; 'cloud.project.id': string; 'cloud.project.name': string; 'cloud.provider': string; 'cloud.region': string; 'cloud.service.name': string; 'container.id': string; 'destination.address': string; 'destination.port': number; 'device.id': string; 'device.manufacturer': string; 'device.model.identifier': string; 'device.model.name': string; 'ecs.version': string; 'error.exception': ", "ApmException", - "[]; 'error.grouping_key': string; 'error.grouping_name': string; 'error.id': string; 'error.type': string; 'event.ingested': number; 'event.name': string; 'event.outcome': string; 'event.outcome_numeric': number | { sum: number; value_count: number; }; 'faas.coldstart': boolean; 'faas.execution': string; 'faas.id': string; 'faas.name': string; 'faas.trigger.type': string; 'faas.version': string; 'host.architecture': string; 'host.hostname': string; 'host.name': string; 'host.os.full': string; 'host.os.name': string; 'host.os.platform': string; 'host.os.type': string; 'host.os.version': string; 'http.request.method': string; 'http.response.status_code': number; 'kubernetes.pod.name': string; 'kubernetes.pod.uid': string; 'labels.name': string; 'labels.telemetry_auto_version': string; 'metricset.name': string; 'network.carrier.icc': string; 'network.carrier.mcc': string; 'network.carrier.mnc': string; 'network.carrier.name': string; 'network.connection.subtype': string; 'network.connection.type': string; 'observer.type': string; 'observer.version_major': number; 'observer.version': string; 'parent.id': string; 'processor.event': string; 'processor.name': string; 'session.id': string; 'trace.id': string; 'transaction.aggregation.overflow_count': number; 'transaction.duration.us': number; 'transaction.id': string; 'transaction.name': string; 'transaction.type': string; 'transaction.duration.histogram': { values: number[]; counts: number[]; }; 'transaction.result': string; 'transaction.sampled': boolean; 'service.environment': string; 'service.framework.name': string; 'service.framework.version': string; 'service.language.name': string; 'service.language.version': string; 'service.name': string; 'service.node.name': string; 'service.runtime.name': string; 'service.runtime.version': string; 'service.target.name': string; 'service.target.type': string; 'service.version': string; 'span.action': string; 'span.destination.service.resource': string; 'span.destination.service.response_time.count': number; 'span.destination.service.response_time.sum.us': number; 'span.duration.us': number; 'span.id': string; 'span.name': string; 'span.self_time.count': number; 'span.self_time.sum.us': number; 'span.subtype': string; 'span.type': string; 'span.links': { trace: { id: string; }; span: { id: string; }; }[]; 'url.original': string; }> & Partial<{ 'system.process.memory.size': number; 'system.memory.actual.free': number; 'system.memory.total': number; 'system.process.cgroup.memory.mem.limit.bytes': number; 'system.process.cgroup.memory.mem.usage.bytes': number; 'system.cpu.total.norm.pct': number; 'system.process.memory.rss.bytes': number; 'system.process.cpu.total.norm.pct': number; 'jvm.memory.heap.used': number; 'jvm.memory.non_heap.used': number; 'jvm.thread.count': number; 'faas.billed_duration': number; 'faas.timeout': number; 'faas.coldstart_duration': number; 'faas.duration': number; 'application.launch.time': number; }> & Partial<{ 'metricset.interval': string; 'transaction.duration.summary': string; }>" + "[]; 'error.grouping_key': string; 'error.grouping_name': string; 'error.id': string; 'error.type': string; 'event.ingested': number; 'event.name': string; 'event.action': string; 'event.outcome': string; 'event.outcome_numeric': number | { sum: number; value_count: number; }; 'faas.coldstart': boolean; 'faas.execution': string; 'faas.id': string; 'faas.name': string; 'faas.trigger.type': string; 'faas.version': string; 'host.architecture': string; 'host.hostname': string; 'host.name': string; 'host.os.full': string; 'host.os.name': string; 'host.os.platform': string; 'host.os.type': string; 'host.os.version': string; 'http.request.method': string; 'http.response.status_code': number; 'kubernetes.pod.name': string; 'kubernetes.pod.uid': string; 'labels.name': string; 'labels.telemetry_auto_version': string; 'labels.lifecycle_state': string; 'metricset.name': string; 'network.carrier.icc': string; 'network.carrier.mcc': string; 'network.carrier.mnc': string; 'network.carrier.name': string; 'network.connection.subtype': string; 'network.connection.type': string; 'observer.type': string; 'observer.version_major': number; 'observer.version': string; 'parent.id': string; 'processor.event': string; 'processor.name': string; 'session.id': string; 'trace.id': string; 'transaction.aggregation.overflow_count': number; 'transaction.duration.us': number; 'transaction.id': string; 'transaction.name': string; 'transaction.type': string; 'transaction.duration.histogram': { values: number[]; counts: number[]; }; 'transaction.result': string; 'transaction.sampled': boolean; 'service.environment': string; 'service.framework.name': string; 'service.framework.version': string; 'service.language.name': string; 'service.language.version': string; 'service.name': string; 'service.node.name': string; 'service.runtime.name': string; 'service.runtime.version': string; 'service.target.name': string; 'service.target.type': string; 'service.version': string; 'span.action': string; 'span.destination.service.resource': string; 'span.destination.service.response_time.count': number; 'span.destination.service.response_time.sum.us': number; 'span.duration.us': number; 'span.id': string; 'span.name': string; 'span.self_time.count': number; 'span.self_time.sum.us': number; 'span.subtype': string; 'span.type': string; 'span.links': { trace: { id: string; }; span: { id: string; }; }[]; 'url.original': string; }> & Partial<{ 'system.process.memory.size': number; 'system.memory.actual.free': number; 'system.memory.total': number; 'system.process.cgroup.memory.mem.limit.bytes': number; 'system.process.cgroup.memory.mem.usage.bytes': number; 'system.cpu.total.norm.pct': number; 'system.process.memory.rss.bytes': number; 'system.process.cpu.total.norm.pct': number; 'jvm.memory.heap.used': number; 'jvm.memory.non_heap.used': number; 'jvm.thread.count': number; 'faas.billed_duration': number; 'faas.timeout': number; 'faas.coldstart_duration': number; 'faas.duration': number; 'application.launch.time': number; }> & Partial<{ 'metricset.interval': string; 'transaction.duration.summary': string; }>" ], "path": "packages/kbn-apm-synthtrace-client/src/lib/apm/apm_fields.ts", "deprecated": false, diff --git a/api_docs/kbn_apm_synthtrace_client.mdx b/api_docs/kbn_apm_synthtrace_client.mdx index d280ab55a1949f..0b9ee3691bb012 100644 --- a/api_docs/kbn_apm_synthtrace_client.mdx +++ b/api_docs/kbn_apm_synthtrace_client.mdx @@ -8,20 +8,20 @@ slug: /kibana-dev-docs/api/kbn-apm-synthtrace-client title: "@kbn/apm-synthtrace-client" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-synthtrace-client plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-synthtrace-client'] --- import kbnApmSynthtraceClientObj from './kbn_apm_synthtrace_client.devdocs.json'; -Contact [@elastic/apm-ui](https://github.com/orgs/elastic/teams/apm-ui) for questions regarding this plugin. +Contact [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) for questions regarding this plugin. **Code health stats** | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 180 | 0 | 180 | 26 | +| 181 | 0 | 181 | 27 | ## Common diff --git a/api_docs/kbn_apm_utils.mdx b/api_docs/kbn_apm_utils.mdx index 8f39485b88c412..774ed2b89f5175 100644 --- a/api_docs/kbn_apm_utils.mdx +++ b/api_docs/kbn_apm_utils.mdx @@ -8,14 +8,14 @@ slug: /kibana-dev-docs/api/kbn-apm-utils title: "@kbn/apm-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-utils plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-utils'] --- import kbnApmUtilsObj from './kbn_apm_utils.devdocs.json'; -Contact [@elastic/apm-ui](https://github.com/orgs/elastic/teams/apm-ui) for questions regarding this plugin. +Contact [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) for questions regarding this plugin. **Code health stats** diff --git a/api_docs/kbn_axe_config.mdx b/api_docs/kbn_axe_config.mdx index 658beedbd07611..db125a6ec32dd6 100644 --- a/api_docs/kbn_axe_config.mdx +++ b/api_docs/kbn_axe_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-axe-config title: "@kbn/axe-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/axe-config plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/axe-config'] --- import kbnAxeConfigObj from './kbn_axe_config.devdocs.json'; diff --git a/api_docs/kbn_cases_components.mdx b/api_docs/kbn_cases_components.mdx index 47a56e98e92cde..265aef6cd10630 100644 --- a/api_docs/kbn_cases_components.mdx +++ b/api_docs/kbn_cases_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cases-components title: "@kbn/cases-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cases-components plugin -date: 2023-11-06 +date: 2023-11-10 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 49ee824435f6f5..f7bff70376d929 100644 --- a/api_docs/kbn_cell_actions.mdx +++ b/api_docs/kbn_cell_actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cell-actions title: "@kbn/cell-actions" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cell-actions plugin -date: 2023-11-06 +date: 2023-11-10 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 d503a695a016c6..9b3ade541eba75 100644 --- a/api_docs/kbn_chart_expressions_common.mdx +++ b/api_docs/kbn_chart_expressions_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-chart-expressions-common title: "@kbn/chart-expressions-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/chart-expressions-common plugin -date: 2023-11-06 +date: 2023-11-10 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 46ff976627a942..eb5afd131b3286 100644 --- a/api_docs/kbn_chart_icons.mdx +++ b/api_docs/kbn_chart_icons.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-chart-icons title: "@kbn/chart-icons" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/chart-icons plugin -date: 2023-11-06 +date: 2023-11-10 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 c46cce68d0aea1..da16197e019612 100644 --- a/api_docs/kbn_ci_stats_core.mdx +++ b/api_docs/kbn_ci_stats_core.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-core title: "@kbn/ci-stats-core" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-core plugin -date: 2023-11-06 +date: 2023-11-10 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 9e5f134b25ed3e..683d382f170e7c 100644 --- a/api_docs/kbn_ci_stats_performance_metrics.mdx +++ b/api_docs/kbn_ci_stats_performance_metrics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-performance-metrics title: "@kbn/ci-stats-performance-metrics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-performance-metrics plugin -date: 2023-11-06 +date: 2023-11-10 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 f8e34e1e87ae89..7e4341104281c7 100644 --- a/api_docs/kbn_ci_stats_reporter.mdx +++ b/api_docs/kbn_ci_stats_reporter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-reporter title: "@kbn/ci-stats-reporter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-reporter plugin -date: 2023-11-06 +date: 2023-11-10 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 c6288c9d1936f4..99384e07907125 100644 --- a/api_docs/kbn_cli_dev_mode.mdx +++ b/api_docs/kbn_cli_dev_mode.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cli-dev-mode title: "@kbn/cli-dev-mode" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cli-dev-mode plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cli-dev-mode'] --- import kbnCliDevModeObj from './kbn_cli_dev_mode.devdocs.json'; diff --git a/api_docs/kbn_code_editor.mdx b/api_docs/kbn_code_editor.mdx index 369e529d75cccf..35ad1eedf78c0b 100644 --- a/api_docs/kbn_code_editor.mdx +++ b/api_docs/kbn_code_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-code-editor title: "@kbn/code-editor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/code-editor plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/code-editor'] --- import kbnCodeEditorObj from './kbn_code_editor.devdocs.json'; diff --git a/api_docs/kbn_coloring.mdx b/api_docs/kbn_coloring.mdx index 36f7869a6d2150..9e51888259b4f4 100644 --- a/api_docs/kbn_coloring.mdx +++ b/api_docs/kbn_coloring.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-coloring title: "@kbn/coloring" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/coloring plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/coloring'] --- import kbnColoringObj from './kbn_coloring.devdocs.json'; diff --git a/api_docs/kbn_config.mdx b/api_docs/kbn_config.mdx index 6afd61e48b6af0..4f8985db93a7f9 100644 --- a/api_docs/kbn_config.mdx +++ b/api_docs/kbn_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config title: "@kbn/config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config plugin -date: 2023-11-06 +date: 2023-11-10 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 2553f43b2a0353..eff87cb5f28449 100644 --- a/api_docs/kbn_config_mocks.mdx +++ b/api_docs/kbn_config_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config-mocks title: "@kbn/config-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 01ef02eb21df10..cbd8024253a418 100644 --- a/api_docs/kbn_config_schema.mdx +++ b/api_docs/kbn_config_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config-schema title: "@kbn/config-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config-schema plugin -date: 2023-11-06 +date: 2023-11-10 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 4862101cab8c6a..1a35d390f9a61f 100644 --- a/api_docs/kbn_content_management_content_editor.mdx +++ b/api_docs/kbn_content_management_content_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-content-editor title: "@kbn/content-management-content-editor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-content-editor plugin -date: 2023-11-06 +date: 2023-11-10 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 8e891ed675ea1e..e6c1a5458e2b52 100644 --- a/api_docs/kbn_content_management_tabbed_table_list_view.mdx +++ b/api_docs/kbn_content_management_tabbed_table_list_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-tabbed-table-list-view title: "@kbn/content-management-tabbed-table-list-view" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-tabbed-table-list-view plugin -date: 2023-11-06 +date: 2023-11-10 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 763bfc7126ce63..ec4e5269bcf645 100644 --- a/api_docs/kbn_content_management_table_list_view.mdx +++ b/api_docs/kbn_content_management_table_list_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-table-list-view title: "@kbn/content-management-table-list-view" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-table-list-view plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-table-list-view'] --- import kbnContentManagementTableListViewObj from './kbn_content_management_table_list_view.devdocs.json'; diff --git a/api_docs/kbn_content_management_table_list_view_table.mdx b/api_docs/kbn_content_management_table_list_view_table.mdx index b9cddc067a278e..b10cae22d456a0 100644 --- a/api_docs/kbn_content_management_table_list_view_table.mdx +++ b/api_docs/kbn_content_management_table_list_view_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-table-list-view-table title: "@kbn/content-management-table-list-view-table" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-table-list-view-table plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-table-list-view-table'] --- import kbnContentManagementTableListViewTableObj from './kbn_content_management_table_list_view_table.devdocs.json'; diff --git a/api_docs/kbn_content_management_utils.mdx b/api_docs/kbn_content_management_utils.mdx index 7ad87dbdb78e88..1469e133d487b9 100644 --- a/api_docs/kbn_content_management_utils.mdx +++ b/api_docs/kbn_content_management_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-utils title: "@kbn/content-management-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-utils plugin -date: 2023-11-06 +date: 2023-11-10 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 50cb401912b7f1..f712106cf8f09a 100644 --- a/api_docs/kbn_core_analytics_browser.mdx +++ b/api_docs/kbn_core_analytics_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser title: "@kbn/core-analytics-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser plugin -date: 2023-11-06 +date: 2023-11-10 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 f987215712db14..6d359fc8194b84 100644 --- a/api_docs/kbn_core_analytics_browser_internal.mdx +++ b/api_docs/kbn_core_analytics_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser-internal title: "@kbn/core-analytics-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser-internal plugin -date: 2023-11-06 +date: 2023-11-10 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 15d293cdced1f6..d3d0d35c6e6926 100644 --- a/api_docs/kbn_core_analytics_browser_mocks.mdx +++ b/api_docs/kbn_core_analytics_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser-mocks title: "@kbn/core-analytics-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 d5ee486ab7bb83..d35a823e2c64eb 100644 --- a/api_docs/kbn_core_analytics_server.mdx +++ b/api_docs/kbn_core_analytics_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server title: "@kbn/core-analytics-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server plugin -date: 2023-11-06 +date: 2023-11-10 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 3b28fab527c553..9512f6077cc793 100644 --- a/api_docs/kbn_core_analytics_server_internal.mdx +++ b/api_docs/kbn_core_analytics_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server-internal title: "@kbn/core-analytics-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server-internal plugin -date: 2023-11-06 +date: 2023-11-10 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 99b4d9ae0c1c91..f968a4405c7415 100644 --- a/api_docs/kbn_core_analytics_server_mocks.mdx +++ b/api_docs/kbn_core_analytics_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server-mocks title: "@kbn/core-analytics-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 0272b938db618a..cbcf773c9d7bb3 100644 --- a/api_docs/kbn_core_application_browser.mdx +++ b/api_docs/kbn_core_application_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser title: "@kbn/core-application-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser plugin -date: 2023-11-06 +date: 2023-11-10 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 91df4b31c1f454..20baf5fda9507b 100644 --- a/api_docs/kbn_core_application_browser_internal.mdx +++ b/api_docs/kbn_core_application_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser-internal title: "@kbn/core-application-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser-internal plugin -date: 2023-11-06 +date: 2023-11-10 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 9109af1c2de31e..9e7dcdcc5b9dbb 100644 --- a/api_docs/kbn_core_application_browser_mocks.mdx +++ b/api_docs/kbn_core_application_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser-mocks title: "@kbn/core-application-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 f09b7b6b97ed3c..b74d90504b20c4 100644 --- a/api_docs/kbn_core_application_common.mdx +++ b/api_docs/kbn_core_application_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-common title: "@kbn/core-application-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-common plugin -date: 2023-11-06 +date: 2023-11-10 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 558d86e5e87e01..8b2967a3980ff5 100644 --- a/api_docs/kbn_core_apps_browser_internal.mdx +++ b/api_docs/kbn_core_apps_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-apps-browser-internal title: "@kbn/core-apps-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-apps-browser-internal plugin -date: 2023-11-06 +date: 2023-11-10 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 552085f579f791..738471d252a26a 100644 --- a/api_docs/kbn_core_apps_browser_mocks.mdx +++ b/api_docs/kbn_core_apps_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-apps-browser-mocks title: "@kbn/core-apps-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-apps-browser-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 462e9a103764bc..1c90a4d719469f 100644 --- a/api_docs/kbn_core_apps_server_internal.mdx +++ b/api_docs/kbn_core_apps_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-apps-server-internal title: "@kbn/core-apps-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-apps-server-internal plugin -date: 2023-11-06 +date: 2023-11-10 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 0984ec76c036be..c65cf3f6c3ace7 100644 --- a/api_docs/kbn_core_base_browser_mocks.mdx +++ b/api_docs/kbn_core_base_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-browser-mocks title: "@kbn/core-base-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-browser-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 f4bd6d661a4017..91d45dcd403bdf 100644 --- a/api_docs/kbn_core_base_common.mdx +++ b/api_docs/kbn_core_base_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-common title: "@kbn/core-base-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-common plugin -date: 2023-11-06 +date: 2023-11-10 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 19a60fc50cf6b2..aad01347e34dd8 100644 --- a/api_docs/kbn_core_base_server_internal.mdx +++ b/api_docs/kbn_core_base_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-server-internal title: "@kbn/core-base-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-server-internal plugin -date: 2023-11-06 +date: 2023-11-10 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 f47c6f3abbb5c0..346f7ae0cdf132 100644 --- a/api_docs/kbn_core_base_server_mocks.mdx +++ b/api_docs/kbn_core_base_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-server-mocks title: "@kbn/core-base-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-server-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 6057a27f595335..4b66e271eb6e97 100644 --- a/api_docs/kbn_core_capabilities_browser_mocks.mdx +++ b/api_docs/kbn_core_capabilities_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-browser-mocks title: "@kbn/core-capabilities-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-browser-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 9dce02a084a069..8c67aaf07c0c2e 100644 --- a/api_docs/kbn_core_capabilities_common.mdx +++ b/api_docs/kbn_core_capabilities_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-common title: "@kbn/core-capabilities-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-common plugin -date: 2023-11-06 +date: 2023-11-10 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.devdocs.json b/api_docs/kbn_core_capabilities_server.devdocs.json index 062051be282232..6c1f0a54810def 100644 --- a/api_docs/kbn_core_capabilities_server.devdocs.json +++ b/api_docs/kbn_core_capabilities_server.devdocs.json @@ -99,6 +99,14 @@ "section": "def-common.CapabilitiesSwitcher", "text": "CapabilitiesSwitcher" }, + ", options: ", + { + "pluginId": "@kbn/core-capabilities-server", + "scope": "common", + "docId": "kibKbnCoreCapabilitiesServerPluginApi", + "section": "def-common.CapabilitiesSwitcherOptions", + "text": "CapabilitiesSwitcherOptions" + }, ") => void" ], "path": "packages/core/capabilities/core-capabilities-server/src/contracts.ts", @@ -125,6 +133,27 @@ "deprecated": false, "trackAdoption": false, "isRequired": true + }, + { + "parentPluginId": "@kbn/core-capabilities-server", + "id": "def-common.CapabilitiesSetup.registerSwitcher.$2", + "type": "Object", + "tags": [], + "label": "options", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-capabilities-server", + "scope": "common", + "docId": "kibKbnCoreCapabilitiesServerPluginApi", + "section": "def-common.CapabilitiesSwitcherOptions", + "text": "CapabilitiesSwitcherOptions" + } + ], + "path": "packages/core/capabilities/core-capabilities-server/src/contracts.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true } ], "returnComment": [] @@ -152,7 +181,7 @@ "tags": [], "label": "resolveCapabilities", "description": [ - "\nResolve the {@link Capabilities} to be used for given request" + "\nResolve the {@link Capabilities} to be used for given request\n" ], "signature": [ "(request: ", @@ -163,7 +192,7 @@ "section": "def-common.KibanaRequest", "text": "KibanaRequest" }, - ", options?: ", + ", options: ", { "pluginId": "@kbn/core-capabilities-server", "scope": "common", @@ -171,7 +200,7 @@ "section": "def-common.ResolveCapabilitiesOptions", "text": "ResolveCapabilitiesOptions" }, - " | undefined) => Promise<", + ") => Promise<", { "pluginId": "@kbn/core-capabilities-common", "scope": "common", @@ -191,7 +220,9 @@ "type": "Object", "tags": [], "label": "request", - "description": [], + "description": [ + "The request to resolve capabilities for" + ], "signature": [ { "pluginId": "@kbn/core-http-server", @@ -221,13 +252,12 @@ "docId": "kibKbnCoreCapabilitiesServerPluginApi", "section": "def-common.ResolveCapabilitiesOptions", "text": "ResolveCapabilitiesOptions" - }, - " | undefined" + } ], "path": "packages/core/capabilities/core-capabilities-server/src/contracts.ts", "deprecated": false, "trackAdoption": false, - "isRequired": false + "isRequired": true } ], "returnComment": [] @@ -235,6 +265,38 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/core-capabilities-server", + "id": "def-common.CapabilitiesSwitcherOptions", + "type": "Interface", + "tags": [], + "label": "CapabilitiesSwitcherOptions", + "description": [ + "\nOptions for the {@link CapabilitiesSetup.registerSwitcher} API.\n" + ], + "path": "packages/core/capabilities/core-capabilities-server/src/contracts.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-capabilities-server", + "id": "def-common.CapabilitiesSwitcherOptions.capabilityPath", + "type": "CompoundType", + "tags": [], + "label": "capabilityPath", + "description": [ + "\nThe path(s) of capabilities the switcher may alter. The '*' wildcard is supported as a suffix only.\n\nE.g. capabilityPath: \"myPlugin.*\" or capabilityPath: \"myPlugin.myKey\"" + ], + "signature": [ + "string | string[]" + ], + "path": "packages/core/capabilities/core-capabilities-server/src/contracts.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/core-capabilities-server", "id": "def-common.ResolveCapabilitiesOptions", @@ -242,20 +304,39 @@ "tags": [], "label": "ResolveCapabilitiesOptions", "description": [ - "\nDefines a set of additional options for the `resolveCapabilities` method of {@link CapabilitiesStart}.\n" + "\nOptions for {@link CapabilitiesStart.resolveCapabilities}.\n" ], "path": "packages/core/capabilities/core-capabilities-server/src/contracts.ts", "deprecated": false, "trackAdoption": false, "children": [ + { + "parentPluginId": "@kbn/core-capabilities-server", + "id": "def-common.ResolveCapabilitiesOptions.capabilityPath", + "type": "CompoundType", + "tags": [], + "label": "capabilityPath", + "description": [ + "\nThe path(s) of capabilities that the API consumer is interested in. The '*' wildcard is supported as a suffix only.\n\nE.g. capabilityPath: \"*\" or capabilityPath: \"myPlugin.*\" or capabilityPath: \"myPlugin.myKey\"\n" + ], + "signature": [ + "string | string[]" + ], + "path": "packages/core/capabilities/core-capabilities-server/src/contracts.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "@kbn/core-capabilities-server", "id": "def-common.ResolveCapabilitiesOptions.useDefaultCapabilities", - "type": "boolean", + "type": "CompoundType", "tags": [], "label": "useDefaultCapabilities", "description": [ - "\nIndicates if capability switchers are supposed to return a default set of capabilities." + "\nIndicates if capability switchers are supposed to return a default set of capabilities.\n\nDefaults to `false`" + ], + "signature": [ + "boolean | undefined" ], "path": "packages/core/capabilities/core-capabilities-server/src/contracts.ts", "deprecated": false, diff --git a/api_docs/kbn_core_capabilities_server.mdx b/api_docs/kbn_core_capabilities_server.mdx index ca7fb6f6c4bbb9..0cc75d1b69d901 100644 --- a/api_docs/kbn_core_capabilities_server.mdx +++ b/api_docs/kbn_core_capabilities_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-server title: "@kbn/core-capabilities-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-server plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-server'] --- import kbnCoreCapabilitiesServerObj from './kbn_core_capabilities_server.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 16 | 0 | 7 | 0 | +| 20 | 0 | 7 | 0 | ## Common diff --git a/api_docs/kbn_core_capabilities_server_mocks.mdx b/api_docs/kbn_core_capabilities_server_mocks.mdx index 91c480e8852685..820464c9b28aae 100644 --- a/api_docs/kbn_core_capabilities_server_mocks.mdx +++ b/api_docs/kbn_core_capabilities_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-server-mocks title: "@kbn/core-capabilities-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-server-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 a4caf2d78ea59f..89a72cbb0cfa8e 100644 --- a/api_docs/kbn_core_chrome_browser.mdx +++ b/api_docs/kbn_core_chrome_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-chrome-browser title: "@kbn/core-chrome-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-chrome-browser plugin -date: 2023-11-06 +date: 2023-11-10 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 62ddfb2070c782..0fa1b61f658da8 100644 --- a/api_docs/kbn_core_chrome_browser_mocks.mdx +++ b/api_docs/kbn_core_chrome_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-chrome-browser-mocks title: "@kbn/core-chrome-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-chrome-browser-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 5e3ccb69708d6e..aaab43682fe35f 100644 --- a/api_docs/kbn_core_config_server_internal.mdx +++ b/api_docs/kbn_core_config_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-config-server-internal title: "@kbn/core-config-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-config-server-internal plugin -date: 2023-11-06 +date: 2023-11-10 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 a90f96336170db..68a60606dcd274 100644 --- a/api_docs/kbn_core_custom_branding_browser.mdx +++ b/api_docs/kbn_core_custom_branding_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-browser title: "@kbn/core-custom-branding-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-browser plugin -date: 2023-11-06 +date: 2023-11-10 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 1837c128252d8d..44cc5278fd53b6 100644 --- a/api_docs/kbn_core_custom_branding_browser_internal.mdx +++ b/api_docs/kbn_core_custom_branding_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-browser-internal title: "@kbn/core-custom-branding-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-browser-internal plugin -date: 2023-11-06 +date: 2023-11-10 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 5495242f088e07..3608be1d96e465 100644 --- a/api_docs/kbn_core_custom_branding_browser_mocks.mdx +++ b/api_docs/kbn_core_custom_branding_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-browser-mocks title: "@kbn/core-custom-branding-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-browser-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 5f559ead82f4cd..9997ac0c27a7c2 100644 --- a/api_docs/kbn_core_custom_branding_common.mdx +++ b/api_docs/kbn_core_custom_branding_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-common title: "@kbn/core-custom-branding-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-common plugin -date: 2023-11-06 +date: 2023-11-10 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 399a0eceeed08d..394ac4e44941da 100644 --- a/api_docs/kbn_core_custom_branding_server.mdx +++ b/api_docs/kbn_core_custom_branding_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-server title: "@kbn/core-custom-branding-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-server plugin -date: 2023-11-06 +date: 2023-11-10 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 e96d80a7446449..30ee602f5fd7b4 100644 --- a/api_docs/kbn_core_custom_branding_server_internal.mdx +++ b/api_docs/kbn_core_custom_branding_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-server-internal title: "@kbn/core-custom-branding-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-server-internal plugin -date: 2023-11-06 +date: 2023-11-10 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 da2649b65ceb7f..2202c66110f4c1 100644 --- a/api_docs/kbn_core_custom_branding_server_mocks.mdx +++ b/api_docs/kbn_core_custom_branding_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-server-mocks title: "@kbn/core-custom-branding-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-server-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 245ee4f0fe1491..e6b20f63b20744 100644 --- a/api_docs/kbn_core_deprecations_browser.mdx +++ b/api_docs/kbn_core_deprecations_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser title: "@kbn/core-deprecations-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser plugin -date: 2023-11-06 +date: 2023-11-10 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 7b3141de682914..78ad726dad0152 100644 --- a/api_docs/kbn_core_deprecations_browser_internal.mdx +++ b/api_docs/kbn_core_deprecations_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser-internal title: "@kbn/core-deprecations-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser-internal plugin -date: 2023-11-06 +date: 2023-11-10 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 3dd7106dd07b0b..2af0d1fde963c9 100644 --- a/api_docs/kbn_core_deprecations_browser_mocks.mdx +++ b/api_docs/kbn_core_deprecations_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser-mocks title: "@kbn/core-deprecations-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 e849f2219e491e..bf788ba7abf12f 100644 --- a/api_docs/kbn_core_deprecations_common.mdx +++ b/api_docs/kbn_core_deprecations_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-common title: "@kbn/core-deprecations-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-common plugin -date: 2023-11-06 +date: 2023-11-10 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 b7a6c72b501d86..464fb71e20fe8e 100644 --- a/api_docs/kbn_core_deprecations_server.mdx +++ b/api_docs/kbn_core_deprecations_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server title: "@kbn/core-deprecations-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server plugin -date: 2023-11-06 +date: 2023-11-10 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 1298b7838451af..3f7a9d7414ed79 100644 --- a/api_docs/kbn_core_deprecations_server_internal.mdx +++ b/api_docs/kbn_core_deprecations_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server-internal title: "@kbn/core-deprecations-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server-internal plugin -date: 2023-11-06 +date: 2023-11-10 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 420a00e540ad98..b045cd749cabb6 100644 --- a/api_docs/kbn_core_deprecations_server_mocks.mdx +++ b/api_docs/kbn_core_deprecations_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server-mocks title: "@kbn/core-deprecations-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 65d22bc086fe13..0be92f27661134 100644 --- a/api_docs/kbn_core_doc_links_browser.mdx +++ b/api_docs/kbn_core_doc_links_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-browser title: "@kbn/core-doc-links-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-browser plugin -date: 2023-11-06 +date: 2023-11-10 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 b8819cbcc3dd83..f4774a5a75447a 100644 --- a/api_docs/kbn_core_doc_links_browser_mocks.mdx +++ b/api_docs/kbn_core_doc_links_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-browser-mocks title: "@kbn/core-doc-links-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-browser-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 b4e8a1c06258bf..3d93b32ce00173 100644 --- a/api_docs/kbn_core_doc_links_server.mdx +++ b/api_docs/kbn_core_doc_links_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-server title: "@kbn/core-doc-links-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-server plugin -date: 2023-11-06 +date: 2023-11-10 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 910644fdf66348..d0c3624f10f6a9 100644 --- a/api_docs/kbn_core_doc_links_server_mocks.mdx +++ b/api_docs/kbn_core_doc_links_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-server-mocks title: "@kbn/core-doc-links-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-server-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 2096d61895acbe..9d7fb20dc9e27e 100644 --- a/api_docs/kbn_core_elasticsearch_client_server_internal.mdx +++ b/api_docs/kbn_core_elasticsearch_client_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-client-server-internal title: "@kbn/core-elasticsearch-client-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-client-server-internal plugin -date: 2023-11-06 +date: 2023-11-10 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 5b4423af4a777d..ca3a588fff62a6 100644 --- a/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx +++ b/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-client-server-mocks title: "@kbn/core-elasticsearch-client-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-client-server-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 be801bd6bacf1e..0315f579853c63 100644 --- a/api_docs/kbn_core_elasticsearch_server.mdx +++ b/api_docs/kbn_core_elasticsearch_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server title: "@kbn/core-elasticsearch-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server plugin -date: 2023-11-06 +date: 2023-11-10 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 adf0dd2707d6c7..2c48581f0b683a 100644 --- a/api_docs/kbn_core_elasticsearch_server_internal.mdx +++ b/api_docs/kbn_core_elasticsearch_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server-internal title: "@kbn/core-elasticsearch-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server-internal plugin -date: 2023-11-06 +date: 2023-11-10 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 a61e93e61c34de..109b228419c354 100644 --- a/api_docs/kbn_core_elasticsearch_server_mocks.mdx +++ b/api_docs/kbn_core_elasticsearch_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server-mocks title: "@kbn/core-elasticsearch-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 18af0311c28f56..f46045bcdf1aab 100644 --- a/api_docs/kbn_core_environment_server_internal.mdx +++ b/api_docs/kbn_core_environment_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-environment-server-internal title: "@kbn/core-environment-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-environment-server-internal plugin -date: 2023-11-06 +date: 2023-11-10 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 d92d0242dd73e9..ed746cb511d112 100644 --- a/api_docs/kbn_core_environment_server_mocks.mdx +++ b/api_docs/kbn_core_environment_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-environment-server-mocks title: "@kbn/core-environment-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-environment-server-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 454747abc8fea9..a280d52377d609 100644 --- a/api_docs/kbn_core_execution_context_browser.mdx +++ b/api_docs/kbn_core_execution_context_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser title: "@kbn/core-execution-context-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser plugin -date: 2023-11-06 +date: 2023-11-10 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 5150662ed0507a..1e527a1aa7a948 100644 --- a/api_docs/kbn_core_execution_context_browser_internal.mdx +++ b/api_docs/kbn_core_execution_context_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser-internal title: "@kbn/core-execution-context-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser-internal plugin -date: 2023-11-06 +date: 2023-11-10 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 b8575a862cdaac..0d3e5418829297 100644 --- a/api_docs/kbn_core_execution_context_browser_mocks.mdx +++ b/api_docs/kbn_core_execution_context_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser-mocks title: "@kbn/core-execution-context-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 fc345b7ff30f26..6b74411a76b275 100644 --- a/api_docs/kbn_core_execution_context_common.mdx +++ b/api_docs/kbn_core_execution_context_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-common title: "@kbn/core-execution-context-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-common plugin -date: 2023-11-06 +date: 2023-11-10 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 f73242712ee034..47ab866be22158 100644 --- a/api_docs/kbn_core_execution_context_server.mdx +++ b/api_docs/kbn_core_execution_context_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server title: "@kbn/core-execution-context-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server plugin -date: 2023-11-06 +date: 2023-11-10 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 9cd869b2e58864..2cafb5f2afa499 100644 --- a/api_docs/kbn_core_execution_context_server_internal.mdx +++ b/api_docs/kbn_core_execution_context_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server-internal title: "@kbn/core-execution-context-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server-internal plugin -date: 2023-11-06 +date: 2023-11-10 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 83fad262144906..92637b63638dfc 100644 --- a/api_docs/kbn_core_execution_context_server_mocks.mdx +++ b/api_docs/kbn_core_execution_context_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server-mocks title: "@kbn/core-execution-context-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 2174108775f5a0..636d191bd921de 100644 --- a/api_docs/kbn_core_fatal_errors_browser.mdx +++ b/api_docs/kbn_core_fatal_errors_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-fatal-errors-browser title: "@kbn/core-fatal-errors-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-fatal-errors-browser plugin -date: 2023-11-06 +date: 2023-11-10 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 9a8cd269633f26..4abfa985ba28e4 100644 --- a/api_docs/kbn_core_fatal_errors_browser_mocks.mdx +++ b/api_docs/kbn_core_fatal_errors_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-fatal-errors-browser-mocks title: "@kbn/core-fatal-errors-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-fatal-errors-browser-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 fc9e1cf37a23f1..958c1086e2672f 100644 --- a/api_docs/kbn_core_http_browser.mdx +++ b/api_docs/kbn_core_http_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser title: "@kbn/core-http-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser plugin -date: 2023-11-06 +date: 2023-11-10 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 48903a443df9c4..fd5258e49d831a 100644 --- a/api_docs/kbn_core_http_browser_internal.mdx +++ b/api_docs/kbn_core_http_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser-internal title: "@kbn/core-http-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser-internal plugin -date: 2023-11-06 +date: 2023-11-10 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 5e050e96162bdc..788743a3aedd43 100644 --- a/api_docs/kbn_core_http_browser_mocks.mdx +++ b/api_docs/kbn_core_http_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser-mocks title: "@kbn/core-http-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 0c563ab7a506f2..2bfde8199d4af8 100644 --- a/api_docs/kbn_core_http_common.mdx +++ b/api_docs/kbn_core_http_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-common title: "@kbn/core-http-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-common plugin -date: 2023-11-06 +date: 2023-11-10 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 063c4701169f65..14e0ba6c45aaea 100644 --- a/api_docs/kbn_core_http_context_server_mocks.mdx +++ b/api_docs/kbn_core_http_context_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-context-server-mocks title: "@kbn/core-http-context-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-context-server-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 87050e3702d92b..67cf62397ce852 100644 --- a/api_docs/kbn_core_http_request_handler_context_server.mdx +++ b/api_docs/kbn_core_http_request_handler_context_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-request-handler-context-server title: "@kbn/core-http-request-handler-context-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-request-handler-context-server plugin -date: 2023-11-06 +date: 2023-11-10 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 4694a0456b52b8..4d5ad2996daf2d 100644 --- a/api_docs/kbn_core_http_resources_server.mdx +++ b/api_docs/kbn_core_http_resources_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-resources-server title: "@kbn/core-http-resources-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-resources-server plugin -date: 2023-11-06 +date: 2023-11-10 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 04f5504cf2dc71..753a7d5f550df1 100644 --- a/api_docs/kbn_core_http_resources_server_internal.mdx +++ b/api_docs/kbn_core_http_resources_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-resources-server-internal title: "@kbn/core-http-resources-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-resources-server-internal plugin -date: 2023-11-06 +date: 2023-11-10 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 bf5682f9d150c6..8ccbc2e1c91f72 100644 --- a/api_docs/kbn_core_http_resources_server_mocks.mdx +++ b/api_docs/kbn_core_http_resources_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-resources-server-mocks title: "@kbn/core-http-resources-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-resources-server-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 937cee57148994..c9d6ae227fb9cc 100644 --- a/api_docs/kbn_core_http_router_server_internal.mdx +++ b/api_docs/kbn_core_http_router_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-router-server-internal title: "@kbn/core-http-router-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-router-server-internal plugin -date: 2023-11-06 +date: 2023-11-10 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 0f4fea73385cfb..c5166c225b4b07 100644 --- a/api_docs/kbn_core_http_router_server_mocks.mdx +++ b/api_docs/kbn_core_http_router_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-router-server-mocks title: "@kbn/core-http-router-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-router-server-mocks plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-router-server-mocks'] --- import kbnCoreHttpRouterServerMocksObj from './kbn_core_http_router_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_server.devdocs.json b/api_docs/kbn_core_http_server.devdocs.json index 35093ec2fe7d73..7798bb1d4ebed3 100644 --- a/api_docs/kbn_core_http_server.devdocs.json +++ b/api_docs/kbn_core_http_server.devdocs.json @@ -105,9 +105,7 @@ "parentPluginId": "@kbn/core-http-server", "id": "def-common.AddVersionOpts", "type": "Interface", - "tags": [ - "experimental" - ], + "tags": [], "label": "AddVersionOpts", "description": [ "\nOptions for a versioned route. Probably needs a lot more options like sunsetting\nof an endpoint etc." @@ -130,9 +128,7 @@ "parentPluginId": "@kbn/core-http-server", "id": "def-common.AddVersionOpts.version", "type": "string", - "tags": [ - "experimental" - ], + "tags": [], "label": "version", "description": [ "\nVersion to assign to this route" @@ -145,9 +141,7 @@ "parentPluginId": "@kbn/core-http-server", "id": "def-common.AddVersionOpts.validate", "type": "CompoundType", - "tags": [ - "experimental" - ], + "tags": [], "label": "validate", "description": [ "\nValidation for this version of a route" @@ -1003,9 +997,7 @@ "parentPluginId": "@kbn/core-http-server", "id": "def-common.FullValidationConfig", "type": "Interface", - "tags": [ - "experimental" - ], + "tags": [], "label": "FullValidationConfig", "description": [ "\nVersioned route validation" @@ -1028,9 +1020,7 @@ "parentPluginId": "@kbn/core-http-server", "id": "def-common.FullValidationConfig.request", "type": "CompoundType", - "tags": [ - "experimental" - ], + "tags": [], "label": "request", "description": [ "\nValidation to run against route inputs: params, query and body" @@ -1054,8 +1044,7 @@ "id": "def-common.FullValidationConfig.response", "type": "Object", "tags": [ - "note", - "experimental" + "note" ], "label": "response", "description": [ @@ -12689,9 +12678,7 @@ "parentPluginId": "@kbn/core-http-server", "id": "def-common.VersionedRoute", "type": "Interface", - "tags": [ - "experimental" - ], + "tags": [], "label": "VersionedRoute", "description": [ "\nA versioned route" @@ -12714,9 +12701,7 @@ "parentPluginId": "@kbn/core-http-server", "id": "def-common.VersionedRoute.addVersion", "type": "Function", - "tags": [ - "experimental" - ], + "tags": [], "label": "addVersion", "description": [ "\nAdd a new version of this route" @@ -12859,9 +12844,7 @@ "parentPluginId": "@kbn/core-http-server", "id": "def-common.VersionedRouter", "type": "Interface", - "tags": [ - "experimental" - ], + "tags": [], "label": "VersionedRouter", "description": [ "\nA router, very similar to {@link IRouter} that will return an {@link VersionedRoute}\ninstead.\n" @@ -12885,7 +12868,6 @@ "id": "def-common.VersionedRouter.get", "type": "Function", "tags": [ - "experimental", "track-adoption" ], "label": "get", @@ -13559,7 +13541,7 @@ }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_status_route.ts" + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/risk_engine_status_route.ts" }, { "plugin": "securitySolution", @@ -13837,7 +13819,6 @@ "id": "def-common.VersionedRouter.put", "type": "Function", "tags": [ - "experimental", "track-adoption" ], "label": "put", @@ -14109,7 +14090,6 @@ "id": "def-common.VersionedRouter.post", "type": "Function", "tags": [ - "experimental", "track-adoption" ], "label": "post", @@ -14273,6 +14253,10 @@ "plugin": "ml", "path": "x-pack/plugins/ml/server/routes/trained_models.ts" }, + { + "plugin": "ml", + "path": "x-pack/plugins/ml/server/routes/trained_models.ts" + }, { "plugin": "ml", "path": "x-pack/plugins/ml/server/routes/data_frame_analytics.ts" @@ -14815,23 +14799,23 @@ }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_score_preview_route.ts" + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/risk_score_preview_route.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_init_route.ts" + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/risk_engine_init_route.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_enable_route.ts" + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/risk_engine_enable_route.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_disable_route.ts" + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/risk_engine_disable_route.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_score_calculation_route.ts" + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/risk_score_calculation_route.ts" }, { "plugin": "securitySolution", @@ -15021,7 +15005,6 @@ "id": "def-common.VersionedRouter.patch", "type": "Function", "tags": [ - "experimental", "track-adoption" ], "label": "patch", @@ -15149,7 +15132,6 @@ "id": "def-common.VersionedRouter.delete", "type": "Function", "tags": [ - "experimental", "track-adoption" ], "label": "delete", @@ -15395,9 +15377,7 @@ "parentPluginId": "@kbn/core-http-server", "id": "def-common.VersionedRouteResponseValidation", "type": "Interface", - "tags": [ - "experimental" - ], + "tags": [], "label": "VersionedRouteResponseValidation", "description": [], "path": "packages/core/http/core-http-server/src/versioning/types.ts", @@ -17840,9 +17820,7 @@ "parentPluginId": "@kbn/core-http-server", "id": "def-common.VersionedRouteConfig", "type": "Type", - "tags": [ - "experimental" - ], + "tags": [], "label": "VersionedRouteConfig", "description": [ "\nConfiguration for a versioned route" @@ -17875,9 +17853,7 @@ "parentPluginId": "@kbn/core-http-server", "id": "def-common.VersionedRouteRegistrar", "type": "Type", - "tags": [ - "experimental" - ], + "tags": [], "label": "VersionedRouteRegistrar", "description": [ "\nCreate an {@link VersionedRoute | versioned route}.\n" @@ -17947,9 +17923,7 @@ "parentPluginId": "@kbn/core-http-server", "id": "def-common.VersionedRouteRequestValidation", "type": "Type", - "tags": [ - "experimental" - ], + "tags": [], "label": "VersionedRouteRequestValidation", "description": [], "signature": [ diff --git a/api_docs/kbn_core_http_server.mdx b/api_docs/kbn_core_http_server.mdx index 65e7c48af456a2..a2c5bb67042a25 100644 --- a/api_docs/kbn_core_http_server.mdx +++ b/api_docs/kbn_core_http_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server title: "@kbn/core-http-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server plugin -date: 2023-11-06 +date: 2023-11-10 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 e05b8e56a92b9d..dd9452034c3bba 100644 --- a/api_docs/kbn_core_http_server_internal.mdx +++ b/api_docs/kbn_core_http_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server-internal title: "@kbn/core-http-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server-internal plugin -date: 2023-11-06 +date: 2023-11-10 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 c4df0b06a8056c..a8db8d4b210639 100644 --- a/api_docs/kbn_core_http_server_mocks.mdx +++ b/api_docs/kbn_core_http_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server-mocks title: "@kbn/core-http-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 303eb54ecb493d..4e787a04108bea 100644 --- a/api_docs/kbn_core_i18n_browser.mdx +++ b/api_docs/kbn_core_i18n_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-browser title: "@kbn/core-i18n-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-browser plugin -date: 2023-11-06 +date: 2023-11-10 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 0609b8e6842234..467a4d2d7775b4 100644 --- a/api_docs/kbn_core_i18n_browser_mocks.mdx +++ b/api_docs/kbn_core_i18n_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-browser-mocks title: "@kbn/core-i18n-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-browser-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 389384c80a7d02..f1956673ae7dd0 100644 --- a/api_docs/kbn_core_i18n_server.mdx +++ b/api_docs/kbn_core_i18n_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server title: "@kbn/core-i18n-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server plugin -date: 2023-11-06 +date: 2023-11-10 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 e0943c08f17652..fb3584a8f3fd30 100644 --- a/api_docs/kbn_core_i18n_server_internal.mdx +++ b/api_docs/kbn_core_i18n_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server-internal title: "@kbn/core-i18n-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server-internal plugin -date: 2023-11-06 +date: 2023-11-10 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 91da426b636bdb..1bffca1a004ab8 100644 --- a/api_docs/kbn_core_i18n_server_mocks.mdx +++ b/api_docs/kbn_core_i18n_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server-mocks title: "@kbn/core-i18n-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 8160283b1961c8..2b66e8eea02749 100644 --- a/api_docs/kbn_core_injected_metadata_browser_mocks.mdx +++ b/api_docs/kbn_core_injected_metadata_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-injected-metadata-browser-mocks title: "@kbn/core-injected-metadata-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-injected-metadata-browser-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 64f981ed16a181..f5623c603bd32e 100644 --- a/api_docs/kbn_core_integrations_browser_internal.mdx +++ b/api_docs/kbn_core_integrations_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-integrations-browser-internal title: "@kbn/core-integrations-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-integrations-browser-internal plugin -date: 2023-11-06 +date: 2023-11-10 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 c6fe70e0ff0f06..502f2a91308d02 100644 --- a/api_docs/kbn_core_integrations_browser_mocks.mdx +++ b/api_docs/kbn_core_integrations_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-integrations-browser-mocks title: "@kbn/core-integrations-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-integrations-browser-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 4162d70bb0f5c4..2b71abfc1ea8a4 100644 --- a/api_docs/kbn_core_lifecycle_browser.mdx +++ b/api_docs/kbn_core_lifecycle_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-browser title: "@kbn/core-lifecycle-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-browser plugin -date: 2023-11-06 +date: 2023-11-10 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 4c9caa7a6da66b..9ca5ba14fbd835 100644 --- a/api_docs/kbn_core_lifecycle_browser_mocks.mdx +++ b/api_docs/kbn_core_lifecycle_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-browser-mocks title: "@kbn/core-lifecycle-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-browser-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 cdb116db938aef..e1df7f4daea4b3 100644 --- a/api_docs/kbn_core_lifecycle_server.mdx +++ b/api_docs/kbn_core_lifecycle_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-server title: "@kbn/core-lifecycle-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-server plugin -date: 2023-11-06 +date: 2023-11-10 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 d54af3f4b5294f..b82e8a97c2d3ad 100644 --- a/api_docs/kbn_core_lifecycle_server_mocks.mdx +++ b/api_docs/kbn_core_lifecycle_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-server-mocks title: "@kbn/core-lifecycle-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-server-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 93a871fcfb5ddd..df33f5414b56cd 100644 --- a/api_docs/kbn_core_logging_browser_mocks.mdx +++ b/api_docs/kbn_core_logging_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-browser-mocks title: "@kbn/core-logging-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-browser-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 c7e01b12ea5ffb..2719840e0362d5 100644 --- a/api_docs/kbn_core_logging_common_internal.mdx +++ b/api_docs/kbn_core_logging_common_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-common-internal title: "@kbn/core-logging-common-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-common-internal plugin -date: 2023-11-06 +date: 2023-11-10 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 b1807ff323f009..65c9b911db15a4 100644 --- a/api_docs/kbn_core_logging_server.mdx +++ b/api_docs/kbn_core_logging_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server title: "@kbn/core-logging-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server plugin -date: 2023-11-06 +date: 2023-11-10 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 9de7633fa5aff8..2e1b08ebff16d5 100644 --- a/api_docs/kbn_core_logging_server_internal.mdx +++ b/api_docs/kbn_core_logging_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server-internal title: "@kbn/core-logging-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server-internal plugin -date: 2023-11-06 +date: 2023-11-10 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 eb640af26f0908..9381b0609991fc 100644 --- a/api_docs/kbn_core_logging_server_mocks.mdx +++ b/api_docs/kbn_core_logging_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server-mocks title: "@kbn/core-logging-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 d295a4b4de80fb..0ed40f70fa83db 100644 --- a/api_docs/kbn_core_metrics_collectors_server_internal.mdx +++ b/api_docs/kbn_core_metrics_collectors_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-collectors-server-internal title: "@kbn/core-metrics-collectors-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-collectors-server-internal plugin -date: 2023-11-06 +date: 2023-11-10 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 c4402587c2035c..f2b43f07cf11ac 100644 --- a/api_docs/kbn_core_metrics_collectors_server_mocks.mdx +++ b/api_docs/kbn_core_metrics_collectors_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-collectors-server-mocks title: "@kbn/core-metrics-collectors-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-collectors-server-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 44187b3a23185d..c184d56ab961f8 100644 --- a/api_docs/kbn_core_metrics_server.mdx +++ b/api_docs/kbn_core_metrics_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server title: "@kbn/core-metrics-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server plugin -date: 2023-11-06 +date: 2023-11-10 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 42071cda02edc5..e690a1a81c64ed 100644 --- a/api_docs/kbn_core_metrics_server_internal.mdx +++ b/api_docs/kbn_core_metrics_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server-internal title: "@kbn/core-metrics-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server-internal plugin -date: 2023-11-06 +date: 2023-11-10 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 51ee6f3848d2b7..57cfb003b40513 100644 --- a/api_docs/kbn_core_metrics_server_mocks.mdx +++ b/api_docs/kbn_core_metrics_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server-mocks title: "@kbn/core-metrics-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 c85f5771682b34..037f00fc1f2d44 100644 --- a/api_docs/kbn_core_mount_utils_browser.mdx +++ b/api_docs/kbn_core_mount_utils_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-mount-utils-browser title: "@kbn/core-mount-utils-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-mount-utils-browser plugin -date: 2023-11-06 +date: 2023-11-10 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 e3976479dac58d..e95d84d0fd7b13 100644 --- a/api_docs/kbn_core_node_server.mdx +++ b/api_docs/kbn_core_node_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server title: "@kbn/core-node-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server plugin -date: 2023-11-06 +date: 2023-11-10 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 f3f7a3cf5842ff..b59392aaeab6df 100644 --- a/api_docs/kbn_core_node_server_internal.mdx +++ b/api_docs/kbn_core_node_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server-internal title: "@kbn/core-node-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server-internal plugin -date: 2023-11-06 +date: 2023-11-10 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 7b8be9ebc79ead..356d844323c814 100644 --- a/api_docs/kbn_core_node_server_mocks.mdx +++ b/api_docs/kbn_core_node_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server-mocks title: "@kbn/core-node-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 eb4401c2b1ddcb..6ee83a5d07fc12 100644 --- a/api_docs/kbn_core_notifications_browser.mdx +++ b/api_docs/kbn_core_notifications_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser title: "@kbn/core-notifications-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser plugin -date: 2023-11-06 +date: 2023-11-10 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 9f41708e32d93c..b9eda3aeff0371 100644 --- a/api_docs/kbn_core_notifications_browser_internal.mdx +++ b/api_docs/kbn_core_notifications_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser-internal title: "@kbn/core-notifications-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser-internal plugin -date: 2023-11-06 +date: 2023-11-10 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 38fe6118989826..304696001866fc 100644 --- a/api_docs/kbn_core_notifications_browser_mocks.mdx +++ b/api_docs/kbn_core_notifications_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser-mocks title: "@kbn/core-notifications-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 7109cfd89caea4..085e40c56353ae 100644 --- a/api_docs/kbn_core_overlays_browser.mdx +++ b/api_docs/kbn_core_overlays_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser title: "@kbn/core-overlays-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser plugin -date: 2023-11-06 +date: 2023-11-10 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 0e3d1f4d83fbb1..3dd4adf128eb06 100644 --- a/api_docs/kbn_core_overlays_browser_internal.mdx +++ b/api_docs/kbn_core_overlays_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser-internal title: "@kbn/core-overlays-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser-internal plugin -date: 2023-11-06 +date: 2023-11-10 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 fbec70283b8d1c..8208fd34bcb45b 100644 --- a/api_docs/kbn_core_overlays_browser_mocks.mdx +++ b/api_docs/kbn_core_overlays_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser-mocks title: "@kbn/core-overlays-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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.devdocs.json b/api_docs/kbn_core_plugins_browser.devdocs.json index 2a0c5a298d8abb..a8013b4ccd8c62 100644 --- a/api_docs/kbn_core_plugins_browser.devdocs.json +++ b/api_docs/kbn_core_plugins_browser.devdocs.json @@ -174,7 +174,15 @@ "label": "stop", "description": [], "signature": [ - "(() => void) | undefined" + "(() => ", + { + "pluginId": "@kbn/utility-types", + "scope": "common", + "docId": "kibKbnUtilityTypesPluginApi", + "section": "def-common.MaybePromise", + "text": "MaybePromise" + }, + ") | undefined" ], "path": "packages/core/plugins/core-plugins-browser/src/plugin.ts", "deprecated": false, diff --git a/api_docs/kbn_core_plugins_browser.mdx b/api_docs/kbn_core_plugins_browser.mdx index 16dbbcdb24dcb1..1602a02dcea983 100644 --- a/api_docs/kbn_core_plugins_browser.mdx +++ b/api_docs/kbn_core_plugins_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-browser title: "@kbn/core-plugins-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-browser plugin -date: 2023-11-06 +date: 2023-11-10 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 622b8d6834675a..46a90d1cfcb72c 100644 --- a/api_docs/kbn_core_plugins_browser_mocks.mdx +++ b/api_docs/kbn_core_plugins_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-browser-mocks title: "@kbn/core-plugins-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-browser-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 8fd9a673311acb..a967dab339293d 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: 2023-11-06 +date: 2023-11-10 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 8ea3be3536491a..90d1e2f22fe492 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: 2023-11-06 +date: 2023-11-10 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.devdocs.json b/api_docs/kbn_core_plugins_server.devdocs.json index a816f657fea806..6de41df4a25aa9 100644 --- a/api_docs/kbn_core_plugins_server.devdocs.json +++ b/api_docs/kbn_core_plugins_server.devdocs.json @@ -191,7 +191,15 @@ "label": "stop", "description": [], "signature": [ - "(() => void) | undefined" + "(() => ", + { + "pluginId": "@kbn/utility-types", + "scope": "common", + "docId": "kibKbnUtilityTypesPluginApi", + "section": "def-common.MaybePromise", + "text": "MaybePromise" + }, + ") | undefined" ], "path": "packages/core/plugins/core-plugins-server/src/types.ts", "deprecated": false, @@ -356,7 +364,15 @@ "label": "stop", "description": [], "signature": [ - "(() => void) | undefined" + "(() => ", + { + "pluginId": "@kbn/utility-types", + "scope": "common", + "docId": "kibKbnUtilityTypesPluginApi", + "section": "def-common.MaybePromise", + "text": "MaybePromise" + }, + ") | undefined" ], "path": "packages/core/plugins/core-plugins-server/src/types.ts", "deprecated": false, diff --git a/api_docs/kbn_core_plugins_server.mdx b/api_docs/kbn_core_plugins_server.mdx index e4569ed3eb2634..dd5d2d6b365bc8 100644 --- a/api_docs/kbn_core_plugins_server.mdx +++ b/api_docs/kbn_core_plugins_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-server title: "@kbn/core-plugins-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-server plugin -date: 2023-11-06 +date: 2023-11-10 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 ac19cb32387bf2..f0978ccf5b1ea6 100644 --- a/api_docs/kbn_core_plugins_server_mocks.mdx +++ b/api_docs/kbn_core_plugins_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-server-mocks title: "@kbn/core-plugins-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-server-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 bc315b8fc4ed21..afdb69e3016356 100644 --- a/api_docs/kbn_core_preboot_server.mdx +++ b/api_docs/kbn_core_preboot_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-preboot-server title: "@kbn/core-preboot-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-preboot-server plugin -date: 2023-11-06 +date: 2023-11-10 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 3fcea666f30954..06b9e164301cba 100644 --- a/api_docs/kbn_core_preboot_server_mocks.mdx +++ b/api_docs/kbn_core_preboot_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-preboot-server-mocks title: "@kbn/core-preboot-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-preboot-server-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 53bffc88b99789..588f6037cd1c6a 100644 --- a/api_docs/kbn_core_rendering_browser_mocks.mdx +++ b/api_docs/kbn_core_rendering_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-rendering-browser-mocks title: "@kbn/core-rendering-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-rendering-browser-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 31c3ffb0282e2a..77eea817ce0c58 100644 --- a/api_docs/kbn_core_rendering_server_internal.mdx +++ b/api_docs/kbn_core_rendering_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-rendering-server-internal title: "@kbn/core-rendering-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-rendering-server-internal plugin -date: 2023-11-06 +date: 2023-11-10 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 1b22f2af1c9454..8513fdbbcad6e1 100644 --- a/api_docs/kbn_core_rendering_server_mocks.mdx +++ b/api_docs/kbn_core_rendering_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-rendering-server-mocks title: "@kbn/core-rendering-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-rendering-server-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 1238344f5b3ad4..e2bedb048a9f8b 100644 --- a/api_docs/kbn_core_root_server_internal.mdx +++ b/api_docs/kbn_core_root_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-root-server-internal title: "@kbn/core-root-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-root-server-internal plugin -date: 2023-11-06 +date: 2023-11-10 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 9df1da111b7db3..181855a71a163b 100644 --- a/api_docs/kbn_core_saved_objects_api_browser.mdx +++ b/api_docs/kbn_core_saved_objects_api_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-browser title: "@kbn/core-saved-objects-api-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-browser plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-browser'] --- import kbnCoreSavedObjectsApiBrowserObj from './kbn_core_saved_objects_api_browser.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_server.mdx b/api_docs/kbn_core_saved_objects_api_server.mdx index dacc1cd31b91f5..bb591388bd00b3 100644 --- a/api_docs/kbn_core_saved_objects_api_server.mdx +++ b/api_docs/kbn_core_saved_objects_api_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server title: "@kbn/core-saved-objects-api-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server plugin -date: 2023-11-06 +date: 2023-11-10 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 27cf381f075649..8c8a4b5806f764 100644 --- a/api_docs/kbn_core_saved_objects_api_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_api_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server-mocks title: "@kbn/core-saved-objects-api-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 c219b14c3d1c5e..32ed91ad28a580 100644 --- a/api_docs/kbn_core_saved_objects_base_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_base_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-base-server-internal title: "@kbn/core-saved-objects-base-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-base-server-internal plugin -date: 2023-11-06 +date: 2023-11-10 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 1f99139e2a09ab..658783e787c8f2 100644 --- a/api_docs/kbn_core_saved_objects_base_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_base_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-base-server-mocks title: "@kbn/core-saved-objects-base-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-base-server-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 55188b52bfa0e1..c4ea045c5d3ee8 100644 --- a/api_docs/kbn_core_saved_objects_browser.mdx +++ b/api_docs/kbn_core_saved_objects_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser title: "@kbn/core-saved-objects-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser plugin -date: 2023-11-06 +date: 2023-11-10 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 30de192e52ef79..42c46012ed39d6 100644 --- a/api_docs/kbn_core_saved_objects_browser_internal.mdx +++ b/api_docs/kbn_core_saved_objects_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser-internal title: "@kbn/core-saved-objects-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser-internal plugin -date: 2023-11-06 +date: 2023-11-10 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 6c676700800570..a9c52995de521a 100644 --- a/api_docs/kbn_core_saved_objects_browser_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser-mocks title: "@kbn/core-saved-objects-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 a9d3fd06e07966..8c2d3b91951093 100644 --- a/api_docs/kbn_core_saved_objects_common.mdx +++ b/api_docs/kbn_core_saved_objects_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-common title: "@kbn/core-saved-objects-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-common plugin -date: 2023-11-06 +date: 2023-11-10 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 569007278a8db8..c37f9837a5166b 100644 --- a/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-import-export-server-internal title: "@kbn/core-saved-objects-import-export-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-import-export-server-internal plugin -date: 2023-11-06 +date: 2023-11-10 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 81466729d80418..5d8322dd56cccb 100644 --- a/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-import-export-server-mocks title: "@kbn/core-saved-objects-import-export-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-import-export-server-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 c1f389aa84e1d1..3ffd7a95e8d430 100644 --- a/api_docs/kbn_core_saved_objects_migration_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_migration_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-migration-server-internal title: "@kbn/core-saved-objects-migration-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-migration-server-internal plugin -date: 2023-11-06 +date: 2023-11-10 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 08b2428e236522..912efca92b4225 100644 --- a/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-migration-server-mocks title: "@kbn/core-saved-objects-migration-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-migration-server-mocks plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-migration-server-mocks'] --- import kbnCoreSavedObjectsMigrationServerMocksObj from './kbn_core_saved_objects_migration_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server.mdx b/api_docs/kbn_core_saved_objects_server.mdx index 023d445224be65..d6ac3fd90180af 100644 --- a/api_docs/kbn_core_saved_objects_server.mdx +++ b/api_docs/kbn_core_saved_objects_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server title: "@kbn/core-saved-objects-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server plugin -date: 2023-11-06 +date: 2023-11-10 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 84131ad41530a5..1d08581d41ead5 100644 --- a/api_docs/kbn_core_saved_objects_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server-internal title: "@kbn/core-saved-objects-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server-internal plugin -date: 2023-11-06 +date: 2023-11-10 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 15105920bb9f97..c56345a83ea353 100644 --- a/api_docs/kbn_core_saved_objects_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server-mocks title: "@kbn/core-saved-objects-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 4159c2de931bb6..48b5572776339f 100644 --- a/api_docs/kbn_core_saved_objects_utils_server.mdx +++ b/api_docs/kbn_core_saved_objects_utils_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-utils-server title: "@kbn/core-saved-objects-utils-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-utils-server plugin -date: 2023-11-06 +date: 2023-11-10 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 87af396a4d74bd..abe953e28d4a00 100644 --- a/api_docs/kbn_core_status_common.mdx +++ b/api_docs/kbn_core_status_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-common title: "@kbn/core-status-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-common plugin -date: 2023-11-06 +date: 2023-11-10 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 71d4f291ce34c4..23392fefa6849c 100644 --- a/api_docs/kbn_core_status_common_internal.mdx +++ b/api_docs/kbn_core_status_common_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-common-internal title: "@kbn/core-status-common-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-common-internal plugin -date: 2023-11-06 +date: 2023-11-10 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 7b89316c51de96..74130005e0b64f 100644 --- a/api_docs/kbn_core_status_server.mdx +++ b/api_docs/kbn_core_status_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server title: "@kbn/core-status-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server plugin -date: 2023-11-06 +date: 2023-11-10 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 a166ef09d4e248..2405cae42ef0c6 100644 --- a/api_docs/kbn_core_status_server_internal.mdx +++ b/api_docs/kbn_core_status_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server-internal title: "@kbn/core-status-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server-internal plugin -date: 2023-11-06 +date: 2023-11-10 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 861b1e4b2b32a2..9a830bc84f8844 100644 --- a/api_docs/kbn_core_status_server_mocks.mdx +++ b/api_docs/kbn_core_status_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server-mocks title: "@kbn/core-status-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 a13ea777ee1cd1..c44712e5ae99a5 100644 --- a/api_docs/kbn_core_test_helpers_deprecations_getters.mdx +++ b/api_docs/kbn_core_test_helpers_deprecations_getters.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-deprecations-getters title: "@kbn/core-test-helpers-deprecations-getters" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-deprecations-getters plugin -date: 2023-11-06 +date: 2023-11-10 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 6c6d2c03cead73..d57a951dc12368 100644 --- a/api_docs/kbn_core_test_helpers_http_setup_browser.mdx +++ b/api_docs/kbn_core_test_helpers_http_setup_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-http-setup-browser title: "@kbn/core-test-helpers-http-setup-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-http-setup-browser plugin -date: 2023-11-06 +date: 2023-11-10 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 32e57cec586b6f..370cc73d276c07 100644 --- a/api_docs/kbn_core_test_helpers_kbn_server.mdx +++ b/api_docs/kbn_core_test_helpers_kbn_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-kbn-server title: "@kbn/core-test-helpers-kbn-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-kbn-server plugin -date: 2023-11-06 +date: 2023-11-10 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 5c117709c852ab..e6b5c40790fcb9 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: 2023-11-06 +date: 2023-11-10 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 4265ccc01e1ff1..cb5f59e90c2805 100644 --- a/api_docs/kbn_core_test_helpers_so_type_serializer.mdx +++ b/api_docs/kbn_core_test_helpers_so_type_serializer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-so-type-serializer title: "@kbn/core-test-helpers-so-type-serializer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-so-type-serializer plugin -date: 2023-11-06 +date: 2023-11-10 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 fb9821e190f92b..e818714e9c4f0e 100644 --- a/api_docs/kbn_core_test_helpers_test_utils.mdx +++ b/api_docs/kbn_core_test_helpers_test_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-test-utils title: "@kbn/core-test-helpers-test-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-test-utils plugin -date: 2023-11-06 +date: 2023-11-10 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 137044204cd16a..cb33695a1bf1b6 100644 --- a/api_docs/kbn_core_theme_browser.mdx +++ b/api_docs/kbn_core_theme_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser title: "@kbn/core-theme-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser plugin -date: 2023-11-06 +date: 2023-11-10 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 e58cc3875fdd74..3eb2726ff8dff9 100644 --- a/api_docs/kbn_core_theme_browser_mocks.mdx +++ b/api_docs/kbn_core_theme_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser-mocks title: "@kbn/core-theme-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 b1b910805aa73a..0f9136192ab628 100644 --- a/api_docs/kbn_core_ui_settings_browser.mdx +++ b/api_docs/kbn_core_ui_settings_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser title: "@kbn/core-ui-settings-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser plugin -date: 2023-11-06 +date: 2023-11-10 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 07ef470ec778fb..ff557a6ee5c84e 100644 --- a/api_docs/kbn_core_ui_settings_browser_internal.mdx +++ b/api_docs/kbn_core_ui_settings_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser-internal title: "@kbn/core-ui-settings-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser-internal plugin -date: 2023-11-06 +date: 2023-11-10 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 1a88d60132c0eb..2437e23b3b0633 100644 --- a/api_docs/kbn_core_ui_settings_browser_mocks.mdx +++ b/api_docs/kbn_core_ui_settings_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser-mocks title: "@kbn/core-ui-settings-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 548ce80556e7ef..589502a6c70fb0 100644 --- a/api_docs/kbn_core_ui_settings_common.mdx +++ b/api_docs/kbn_core_ui_settings_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-common title: "@kbn/core-ui-settings-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-common plugin -date: 2023-11-06 +date: 2023-11-10 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 356222489dcf90..695476f91b9129 100644 --- a/api_docs/kbn_core_ui_settings_server.mdx +++ b/api_docs/kbn_core_ui_settings_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server title: "@kbn/core-ui-settings-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server plugin -date: 2023-11-06 +date: 2023-11-10 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 fe290c1e5c68ae..b4c5a99ac8c7ca 100644 --- a/api_docs/kbn_core_ui_settings_server_internal.mdx +++ b/api_docs/kbn_core_ui_settings_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server-internal title: "@kbn/core-ui-settings-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server-internal plugin -date: 2023-11-06 +date: 2023-11-10 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 d85cf87629dafe..81e05e971d7b97 100644 --- a/api_docs/kbn_core_ui_settings_server_mocks.mdx +++ b/api_docs/kbn_core_ui_settings_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server-mocks title: "@kbn/core-ui-settings-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 8945251f7a62c7..0aaf205f33fbd5 100644 --- a/api_docs/kbn_core_usage_data_server.mdx +++ b/api_docs/kbn_core_usage_data_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server title: "@kbn/core-usage-data-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server plugin -date: 2023-11-06 +date: 2023-11-10 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 3f589798c1486d..f325a7cd1500f8 100644 --- a/api_docs/kbn_core_usage_data_server_internal.mdx +++ b/api_docs/kbn_core_usage_data_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server-internal title: "@kbn/core-usage-data-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server-internal plugin -date: 2023-11-06 +date: 2023-11-10 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 cbf8160fcb7711..65f3204acdfcbd 100644 --- a/api_docs/kbn_core_usage_data_server_mocks.mdx +++ b/api_docs/kbn_core_usage_data_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server-mocks title: "@kbn/core-usage-data-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 854bf64adc2f6e..036325e5cc1fec 100644 --- a/api_docs/kbn_core_user_settings_server.mdx +++ b/api_docs/kbn_core_user_settings_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-settings-server title: "@kbn/core-user-settings-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-settings-server plugin -date: 2023-11-06 +date: 2023-11-10 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 ccec1df55605c7..c5c04501972416 100644 --- a/api_docs/kbn_core_user_settings_server_internal.mdx +++ b/api_docs/kbn_core_user_settings_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-settings-server-internal title: "@kbn/core-user-settings-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-settings-server-internal plugin -date: 2023-11-06 +date: 2023-11-10 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 293535e3b8f337..8c6fa7792422aa 100644 --- a/api_docs/kbn_core_user_settings_server_mocks.mdx +++ b/api_docs/kbn_core_user_settings_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-settings-server-mocks title: "@kbn/core-user-settings-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-settings-server-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 86235caafd768f..3557220b3b2a64 100644 --- a/api_docs/kbn_crypto.mdx +++ b/api_docs/kbn_crypto.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-crypto title: "@kbn/crypto" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/crypto plugin -date: 2023-11-06 +date: 2023-11-10 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 ed71b5d9de46b0..6790f3501e4ad7 100644 --- a/api_docs/kbn_crypto_browser.mdx +++ b/api_docs/kbn_crypto_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-crypto-browser title: "@kbn/crypto-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/crypto-browser plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/crypto-browser'] --- import kbnCryptoBrowserObj from './kbn_crypto_browser.devdocs.json'; diff --git a/api_docs/kbn_custom_integrations.mdx b/api_docs/kbn_custom_integrations.mdx index 63552c587a08f3..14801203ee2e87 100644 --- a/api_docs/kbn_custom_integrations.mdx +++ b/api_docs/kbn_custom_integrations.mdx @@ -8,14 +8,14 @@ slug: /kibana-dev-docs/api/kbn-custom-integrations title: "@kbn/custom-integrations" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/custom-integrations plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/custom-integrations'] --- import kbnCustomIntegrationsObj from './kbn_custom_integrations.devdocs.json'; -Contact [@elastic/infra-monitoring-ui](https://github.com/orgs/elastic/teams/infra-monitoring-ui) for questions regarding this plugin. +Contact [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) for questions regarding this plugin. **Code health stats** diff --git a/api_docs/kbn_cypress_config.mdx b/api_docs/kbn_cypress_config.mdx index 32096f38f1c13f..806ddd39465f05 100644 --- a/api_docs/kbn_cypress_config.mdx +++ b/api_docs/kbn_cypress_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cypress-config title: "@kbn/cypress-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cypress-config plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cypress-config'] --- import kbnCypressConfigObj from './kbn_cypress_config.devdocs.json'; diff --git a/api_docs/kbn_data_service.mdx b/api_docs/kbn_data_service.mdx index 9186a7aecee62e..7cf005cbc28095 100644 --- a/api_docs/kbn_data_service.mdx +++ b/api_docs/kbn_data_service.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-data-service title: "@kbn/data-service" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/data-service plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/data-service'] --- import kbnDataServiceObj from './kbn_data_service.devdocs.json'; diff --git a/api_docs/kbn_datemath.mdx b/api_docs/kbn_datemath.mdx index 52fb2a0f076411..563687e0ec1ede 100644 --- a/api_docs/kbn_datemath.mdx +++ b/api_docs/kbn_datemath.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-datemath title: "@kbn/datemath" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/datemath plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/datemath'] --- import kbnDatemathObj from './kbn_datemath.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_analytics.mdx b/api_docs/kbn_deeplinks_analytics.mdx index b02ba6219ff5a7..45c0fa05d081cd 100644 --- a/api_docs/kbn_deeplinks_analytics.mdx +++ b/api_docs/kbn_deeplinks_analytics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-analytics title: "@kbn/deeplinks-analytics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-analytics plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-analytics'] --- import kbnDeeplinksAnalyticsObj from './kbn_deeplinks_analytics.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_devtools.mdx b/api_docs/kbn_deeplinks_devtools.mdx index f3b528551b39f3..f804a844ff8e14 100644 --- a/api_docs/kbn_deeplinks_devtools.mdx +++ b/api_docs/kbn_deeplinks_devtools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-devtools title: "@kbn/deeplinks-devtools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-devtools plugin -date: 2023-11-06 +date: 2023-11-10 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 e2963385d3d04c..4f92dbe05e00b8 100644 --- a/api_docs/kbn_deeplinks_management.mdx +++ b/api_docs/kbn_deeplinks_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-management title: "@kbn/deeplinks-management" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-management plugin -date: 2023-11-06 +date: 2023-11-10 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 c637e78b72da87..7c1d93f0b18f8b 100644 --- a/api_docs/kbn_deeplinks_ml.mdx +++ b/api_docs/kbn_deeplinks_ml.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-ml title: "@kbn/deeplinks-ml" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-ml plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-ml'] --- import kbnDeeplinksMlObj from './kbn_deeplinks_ml.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_observability.mdx b/api_docs/kbn_deeplinks_observability.mdx index 8dce90566f06dc..b942771d9a391a 100644 --- a/api_docs/kbn_deeplinks_observability.mdx +++ b/api_docs/kbn_deeplinks_observability.mdx @@ -8,14 +8,14 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-observability title: "@kbn/deeplinks-observability" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-observability plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-observability'] --- import kbnDeeplinksObservabilityObj from './kbn_deeplinks_observability.devdocs.json'; -Contact [@elastic/apm-ui](https://github.com/orgs/elastic/teams/apm-ui) for questions regarding this plugin. +Contact [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) for questions regarding this plugin. **Code health stats** diff --git a/api_docs/kbn_deeplinks_search.mdx b/api_docs/kbn_deeplinks_search.mdx index bd97570d5b48b2..968890a1990268 100644 --- a/api_docs/kbn_deeplinks_search.mdx +++ b/api_docs/kbn_deeplinks_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-search title: "@kbn/deeplinks-search" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-search plugin -date: 2023-11-06 +date: 2023-11-10 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 d1c765e6049d5c..bf90f6f8018c1e 100644 --- a/api_docs/kbn_default_nav_analytics.mdx +++ b/api_docs/kbn_default_nav_analytics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-default-nav-analytics title: "@kbn/default-nav-analytics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/default-nav-analytics plugin -date: 2023-11-06 +date: 2023-11-10 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 2af7a9c63ba77c..55cdf9fb776c71 100644 --- a/api_docs/kbn_default_nav_devtools.mdx +++ b/api_docs/kbn_default_nav_devtools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-default-nav-devtools title: "@kbn/default-nav-devtools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/default-nav-devtools plugin -date: 2023-11-06 +date: 2023-11-10 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 cf3a5de1e5fd45..05484e1161112f 100644 --- a/api_docs/kbn_default_nav_management.mdx +++ b/api_docs/kbn_default_nav_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-default-nav-management title: "@kbn/default-nav-management" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/default-nav-management plugin -date: 2023-11-06 +date: 2023-11-10 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 c4ab644aa70a2c..a45fdbc62116cb 100644 --- a/api_docs/kbn_default_nav_ml.mdx +++ b/api_docs/kbn_default_nav_ml.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-default-nav-ml title: "@kbn/default-nav-ml" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/default-nav-ml plugin -date: 2023-11-06 +date: 2023-11-10 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 b7740d856f2bf1..c70a020354634e 100644 --- a/api_docs/kbn_dev_cli_errors.mdx +++ b/api_docs/kbn_dev_cli_errors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-cli-errors title: "@kbn/dev-cli-errors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-cli-errors plugin -date: 2023-11-06 +date: 2023-11-10 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 350e5235fd21e5..6516a520cf45b8 100644 --- a/api_docs/kbn_dev_cli_runner.mdx +++ b/api_docs/kbn_dev_cli_runner.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-cli-runner title: "@kbn/dev-cli-runner" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-cli-runner plugin -date: 2023-11-06 +date: 2023-11-10 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 37bd0588bcc704..422ea43ace47cb 100644 --- a/api_docs/kbn_dev_proc_runner.mdx +++ b/api_docs/kbn_dev_proc_runner.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-proc-runner title: "@kbn/dev-proc-runner" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-proc-runner plugin -date: 2023-11-06 +date: 2023-11-10 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 bb8059db376aeb..8a1e67a87ccd6a 100644 --- a/api_docs/kbn_dev_utils.mdx +++ b/api_docs/kbn_dev_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-utils title: "@kbn/dev-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-utils plugin -date: 2023-11-06 +date: 2023-11-10 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 b49185f7bca7e1..9f15d474a0cb71 100644 --- a/api_docs/kbn_discover_utils.mdx +++ b/api_docs/kbn_discover_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-discover-utils title: "@kbn/discover-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/discover-utils plugin -date: 2023-11-06 +date: 2023-11-10 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 6ae416fe32bda5..d132a523f673e8 100644 --- a/api_docs/kbn_doc_links.mdx +++ b/api_docs/kbn_doc_links.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-doc-links title: "@kbn/doc-links" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/doc-links plugin -date: 2023-11-06 +date: 2023-11-10 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 9d154b7ad57041..1435adc05aa9a1 100644 --- a/api_docs/kbn_docs_utils.mdx +++ b/api_docs/kbn_docs_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-docs-utils title: "@kbn/docs-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/docs-utils plugin -date: 2023-11-06 +date: 2023-11-10 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 18d074a2d8b184..cf006d20f611ac 100644 --- a/api_docs/kbn_dom_drag_drop.mdx +++ b/api_docs/kbn_dom_drag_drop.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dom-drag-drop title: "@kbn/dom-drag-drop" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dom-drag-drop plugin -date: 2023-11-06 +date: 2023-11-10 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 07321a36acac8a..1dd79a773c680a 100644 --- a/api_docs/kbn_ebt_tools.mdx +++ b/api_docs/kbn_ebt_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ebt-tools title: "@kbn/ebt-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ebt-tools plugin -date: 2023-11-06 +date: 2023-11-10 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 8f795ed80b6ff5..ac42a93c6248f4 100644 --- a/api_docs/kbn_ecs.mdx +++ b/api_docs/kbn_ecs.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ecs title: "@kbn/ecs" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ecs plugin -date: 2023-11-06 +date: 2023-11-10 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 255c1244a92bbe..655633477010a1 100644 --- a/api_docs/kbn_ecs_data_quality_dashboard.mdx +++ b/api_docs/kbn_ecs_data_quality_dashboard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ecs-data-quality-dashboard title: "@kbn/ecs-data-quality-dashboard" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ecs-data-quality-dashboard plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ecs-data-quality-dashboard'] --- import kbnEcsDataQualityDashboardObj from './kbn_ecs_data_quality_dashboard.devdocs.json'; diff --git a/api_docs/kbn_elastic_assistant.devdocs.json b/api_docs/kbn_elastic_assistant.devdocs.json index f97b2b7e4528e6..d308ab08ff13ac 100644 --- a/api_docs/kbn_elastic_assistant.devdocs.json +++ b/api_docs/kbn_elastic_assistant.devdocs.json @@ -956,6 +956,20 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "@kbn/elastic-assistant", + "id": "def-public.Message.reader", + "type": "Object", + "tags": [], + "label": "reader", + "description": [], + "signature": [ + "ReadableStreamDefaultReader | undefined" + ], + "path": "x-pack/packages/kbn-elastic-assistant/impl/assistant_context/types.tsx", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "@kbn/elastic-assistant", "id": "def-public.Message.content", @@ -963,6 +977,9 @@ "tags": [], "label": "content", "description": [], + "signature": [ + "string | undefined" + ], "path": "x-pack/packages/kbn-elastic-assistant/impl/assistant_context/types.tsx", "deprecated": false, "trackAdoption": false diff --git a/api_docs/kbn_elastic_assistant.mdx b/api_docs/kbn_elastic_assistant.mdx index 07b481886264a4..8c64e7d276b935 100644 --- a/api_docs/kbn_elastic_assistant.mdx +++ b/api_docs/kbn_elastic_assistant.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-elastic-assistant title: "@kbn/elastic-assistant" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/elastic-assistant plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/elastic-assistant'] --- import kbnElasticAssistantObj from './kbn_elastic_assistant.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/security-solution](https://github.com/orgs/elastic/teams/secur | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 95 | 0 | 75 | 6 | +| 96 | 0 | 76 | 6 | ## Client diff --git a/api_docs/kbn_es.devdocs.json b/api_docs/kbn_es.devdocs.json index bd54f768077c24..00729743dcb2e8 100644 --- a/api_docs/kbn_es.devdocs.json +++ b/api_docs/kbn_es.devdocs.json @@ -797,7 +797,7 @@ "label": "ArtifactLicense", "description": [], "signature": [ - "\"basic\" | \"trial\" | \"oss\"" + "\"basic\" | \"trial\"" ], "path": "packages/kbn-es/src/artifact.ts", "deprecated": false, diff --git a/api_docs/kbn_es.mdx b/api_docs/kbn_es.mdx index 3d5e9936b28e5c..9b6899d046e79a 100644 --- a/api_docs/kbn_es.mdx +++ b/api_docs/kbn_es.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es title: "@kbn/es" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es plugin -date: 2023-11-06 +date: 2023-11-10 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 c3c819b4d3f649..cfde8f51320946 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: 2023-11-06 +date: 2023-11-10 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 e57df98f2148c9..ee15d944ea53f4 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: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-errors'] --- import kbnEsErrorsObj from './kbn_es_errors.devdocs.json'; diff --git a/api_docs/kbn_es_query.mdx b/api_docs/kbn_es_query.mdx index 5e8e05dedac428..a54cfc339e5e1b 100644 --- a/api_docs/kbn_es_query.mdx +++ b/api_docs/kbn_es_query.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-query title: "@kbn/es-query" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-query plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-query'] --- import kbnEsQueryObj from './kbn_es_query.devdocs.json'; diff --git a/api_docs/kbn_es_types.mdx b/api_docs/kbn_es_types.mdx index ebef090c1200e6..2b438ddf0fc73d 100644 --- a/api_docs/kbn_es_types.mdx +++ b/api_docs/kbn_es_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-types title: "@kbn/es-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-types plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-types'] --- import kbnEsTypesObj from './kbn_es_types.devdocs.json'; diff --git a/api_docs/kbn_eslint_plugin_imports.mdx b/api_docs/kbn_eslint_plugin_imports.mdx index 2399c0ffdd4195..7ac64671251ed1 100644 --- a/api_docs/kbn_eslint_plugin_imports.mdx +++ b/api_docs/kbn_eslint_plugin_imports.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-eslint-plugin-imports title: "@kbn/eslint-plugin-imports" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/eslint-plugin-imports plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/eslint-plugin-imports'] --- import kbnEslintPluginImportsObj from './kbn_eslint_plugin_imports.devdocs.json'; diff --git a/api_docs/kbn_event_annotation_common.mdx b/api_docs/kbn_event_annotation_common.mdx index 567eb30a3c5532..98447ee7d00e8e 100644 --- a/api_docs/kbn_event_annotation_common.mdx +++ b/api_docs/kbn_event_annotation_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-event-annotation-common title: "@kbn/event-annotation-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/event-annotation-common plugin -date: 2023-11-06 +date: 2023-11-10 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 0d42d81c7e6d10..d9d7f28c771834 100644 --- a/api_docs/kbn_event_annotation_components.mdx +++ b/api_docs/kbn_event_annotation_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-event-annotation-components title: "@kbn/event-annotation-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/event-annotation-components plugin -date: 2023-11-06 +date: 2023-11-10 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 4a33149772150a..282dcd8a40ce07 100644 --- a/api_docs/kbn_expandable_flyout.mdx +++ b/api_docs/kbn_expandable_flyout.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-expandable-flyout title: "@kbn/expandable-flyout" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/expandable-flyout plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/expandable-flyout'] --- import kbnExpandableFlyoutObj from './kbn_expandable_flyout.devdocs.json'; diff --git a/api_docs/kbn_field_types.mdx b/api_docs/kbn_field_types.mdx index dbe71e82c8cb31..444f905a6ad60d 100644 --- a/api_docs/kbn_field_types.mdx +++ b/api_docs/kbn_field_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-field-types title: "@kbn/field-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/field-types plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/field-types'] --- import kbnFieldTypesObj from './kbn_field_types.devdocs.json'; diff --git a/api_docs/kbn_field_utils.mdx b/api_docs/kbn_field_utils.mdx index bae97779b76efd..4af2dd125e4180 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: 2023-11-06 +date: 2023-11-10 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 8577f8d6f6f5b5..677aeb3cda9a9e 100644 --- a/api_docs/kbn_find_used_node_modules.mdx +++ b/api_docs/kbn_find_used_node_modules.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-find-used-node-modules title: "@kbn/find-used-node-modules" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/find-used-node-modules plugin -date: 2023-11-06 +date: 2023-11-10 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 3369f19e15c70f..938d20fe119a1b 100644 --- a/api_docs/kbn_ftr_common_functional_services.mdx +++ b/api_docs/kbn_ftr_common_functional_services.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ftr-common-functional-services title: "@kbn/ftr-common-functional-services" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ftr-common-functional-services plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ftr-common-functional-services'] --- import kbnFtrCommonFunctionalServicesObj from './kbn_ftr_common_functional_services.devdocs.json'; diff --git a/api_docs/kbn_generate.mdx b/api_docs/kbn_generate.mdx index 39ce52068d9df3..5802e421aab209 100644 --- a/api_docs/kbn_generate.mdx +++ b/api_docs/kbn_generate.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-generate title: "@kbn/generate" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/generate plugin -date: 2023-11-06 +date: 2023-11-10 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 24bb6bb491517f..cc00c519f68430 100644 --- a/api_docs/kbn_generate_console_definitions.mdx +++ b/api_docs/kbn_generate_console_definitions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-generate-console-definitions title: "@kbn/generate-console-definitions" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/generate-console-definitions plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate-console-definitions'] --- import kbnGenerateConsoleDefinitionsObj from './kbn_generate_console_definitions.devdocs.json'; diff --git a/api_docs/kbn_generate_csv.mdx b/api_docs/kbn_generate_csv.mdx index 14e79d00bdfd1a..1f6af6e0ce386f 100644 --- a/api_docs/kbn_generate_csv.mdx +++ b/api_docs/kbn_generate_csv.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-generate-csv title: "@kbn/generate-csv" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/generate-csv plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate-csv'] --- import kbnGenerateCsvObj from './kbn_generate_csv.devdocs.json'; diff --git a/api_docs/kbn_generate_csv_types.mdx b/api_docs/kbn_generate_csv_types.mdx index 8caa926d37536f..a4127854b3fc91 100644 --- a/api_docs/kbn_generate_csv_types.mdx +++ b/api_docs/kbn_generate_csv_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-generate-csv-types title: "@kbn/generate-csv-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/generate-csv-types plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate-csv-types'] --- import kbnGenerateCsvTypesObj from './kbn_generate_csv_types.devdocs.json'; diff --git a/api_docs/kbn_guided_onboarding.mdx b/api_docs/kbn_guided_onboarding.mdx index c3363f8a87c021..7ca43517a70581 100644 --- a/api_docs/kbn_guided_onboarding.mdx +++ b/api_docs/kbn_guided_onboarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-guided-onboarding title: "@kbn/guided-onboarding" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/guided-onboarding plugin -date: 2023-11-06 +date: 2023-11-10 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 e76c15573165c4..4d30fb27efc6f1 100644 --- a/api_docs/kbn_handlebars.mdx +++ b/api_docs/kbn_handlebars.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-handlebars title: "@kbn/handlebars" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/handlebars plugin -date: 2023-11-06 +date: 2023-11-10 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 cfbaabe2534718..c274bb3eb113a3 100644 --- a/api_docs/kbn_hapi_mocks.mdx +++ b/api_docs/kbn_hapi_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-hapi-mocks title: "@kbn/hapi-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/hapi-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 0fcc2fc3cfa219..ff88c770f0a925 100644 --- a/api_docs/kbn_health_gateway_server.mdx +++ b/api_docs/kbn_health_gateway_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-health-gateway-server title: "@kbn/health-gateway-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/health-gateway-server plugin -date: 2023-11-06 +date: 2023-11-10 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 f0e1f0ea9d7183..73fec5b9715f90 100644 --- a/api_docs/kbn_home_sample_data_card.mdx +++ b/api_docs/kbn_home_sample_data_card.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-home-sample-data-card title: "@kbn/home-sample-data-card" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/home-sample-data-card plugin -date: 2023-11-06 +date: 2023-11-10 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 6dabef4fe062c4..74724f71aac4a3 100644 --- a/api_docs/kbn_home_sample_data_tab.mdx +++ b/api_docs/kbn_home_sample_data_tab.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-home-sample-data-tab title: "@kbn/home-sample-data-tab" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/home-sample-data-tab plugin -date: 2023-11-06 +date: 2023-11-10 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 e5140cb3ecd5ed..316a26ab2987b8 100644 --- a/api_docs/kbn_i18n.mdx +++ b/api_docs/kbn_i18n.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-i18n title: "@kbn/i18n" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/i18n plugin -date: 2023-11-06 +date: 2023-11-10 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 d0dc5f0fdf1c73..152f7990d01f51 100644 --- a/api_docs/kbn_i18n_react.mdx +++ b/api_docs/kbn_i18n_react.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-i18n-react title: "@kbn/i18n-react" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/i18n-react plugin -date: 2023-11-06 +date: 2023-11-10 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 e5b72598ad3085..c8d453d363af05 100644 --- a/api_docs/kbn_import_resolver.mdx +++ b/api_docs/kbn_import_resolver.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-import-resolver title: "@kbn/import-resolver" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/import-resolver plugin -date: 2023-11-06 +date: 2023-11-10 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 fe948ba1420d5f..c80ba436eedca9 100644 --- a/api_docs/kbn_infra_forge.mdx +++ b/api_docs/kbn_infra_forge.mdx @@ -8,14 +8,14 @@ slug: /kibana-dev-docs/api/kbn-infra-forge title: "@kbn/infra-forge" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/infra-forge plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/infra-forge'] --- import kbnInfraForgeObj from './kbn_infra_forge.devdocs.json'; -Contact [@elastic/actionable-observability](https://github.com/orgs/elastic/teams/actionable-observability) for questions regarding this plugin. +Contact [@elastic/obs-ux-management-team](https://github.com/orgs/elastic/teams/obs-ux-management-team) for questions regarding this plugin. **Code health stats** diff --git a/api_docs/kbn_interpreter.mdx b/api_docs/kbn_interpreter.mdx index 92eddd1a168ea5..f7678f9890fc65 100644 --- a/api_docs/kbn_interpreter.mdx +++ b/api_docs/kbn_interpreter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-interpreter title: "@kbn/interpreter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/interpreter plugin -date: 2023-11-06 +date: 2023-11-10 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 552487bdbfc85b..010c1ade34cb48 100644 --- a/api_docs/kbn_io_ts_utils.mdx +++ b/api_docs/kbn_io_ts_utils.mdx @@ -8,14 +8,14 @@ slug: /kibana-dev-docs/api/kbn-io-ts-utils title: "@kbn/io-ts-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/io-ts-utils plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/io-ts-utils'] --- import kbnIoTsUtilsObj from './kbn_io_ts_utils.devdocs.json'; -Contact [@elastic/apm-ui](https://github.com/orgs/elastic/teams/apm-ui) for questions regarding this plugin. +Contact [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs-knowledge-team) for questions regarding this plugin. **Code health stats** diff --git a/api_docs/kbn_jest_serializers.mdx b/api_docs/kbn_jest_serializers.mdx index 319e733e8896cc..6f153c672e7eb0 100644 --- a/api_docs/kbn_jest_serializers.mdx +++ b/api_docs/kbn_jest_serializers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-jest-serializers title: "@kbn/jest-serializers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/jest-serializers plugin -date: 2023-11-06 +date: 2023-11-10 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 034fa03ce818b0..21be3675a11aab 100644 --- a/api_docs/kbn_journeys.mdx +++ b/api_docs/kbn_journeys.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-journeys title: "@kbn/journeys" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/journeys plugin -date: 2023-11-06 +date: 2023-11-10 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 ebd5cd88823a63..ef11a7da0bf968 100644 --- a/api_docs/kbn_json_ast.mdx +++ b/api_docs/kbn_json_ast.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-json-ast title: "@kbn/json-ast" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/json-ast plugin -date: 2023-11-06 +date: 2023-11-10 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 f46f083df53471..5de6d034f507d0 100644 --- a/api_docs/kbn_kibana_manifest_schema.mdx +++ b/api_docs/kbn_kibana_manifest_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-kibana-manifest-schema title: "@kbn/kibana-manifest-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/kibana-manifest-schema plugin -date: 2023-11-06 +date: 2023-11-10 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 cc34b6d2c7e048..45dac881957934 100644 --- a/api_docs/kbn_language_documentation_popover.mdx +++ b/api_docs/kbn_language_documentation_popover.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-language-documentation-popover title: "@kbn/language-documentation-popover" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/language-documentation-popover plugin -date: 2023-11-06 +date: 2023-11-10 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 0f6d06d38b82a7..2f58d6fea986c1 100644 --- a/api_docs/kbn_lens_embeddable_utils.mdx +++ b/api_docs/kbn_lens_embeddable_utils.mdx @@ -8,14 +8,14 @@ slug: /kibana-dev-docs/api/kbn-lens-embeddable-utils title: "@kbn/lens-embeddable-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/lens-embeddable-utils plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/lens-embeddable-utils'] --- import kbnLensEmbeddableUtilsObj from './kbn_lens_embeddable_utils.devdocs.json'; -Contact [@elastic/infra-monitoring-ui](https://github.com/orgs/elastic/teams/infra-monitoring-ui) for questions regarding this plugin. +Contact [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) for questions regarding this plugin. **Code health stats** diff --git a/api_docs/kbn_logging.mdx b/api_docs/kbn_logging.mdx index 51c4a1c18feb20..d4d5fbe85d7938 100644 --- a/api_docs/kbn_logging.mdx +++ b/api_docs/kbn_logging.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-logging title: "@kbn/logging" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/logging plugin -date: 2023-11-06 +date: 2023-11-10 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 7e1bd5cb04bfd5..59f4fb2b93434c 100644 --- a/api_docs/kbn_logging_mocks.mdx +++ b/api_docs/kbn_logging_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-logging-mocks title: "@kbn/logging-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/logging-mocks plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/logging-mocks'] --- import kbnLoggingMocksObj from './kbn_logging_mocks.devdocs.json'; diff --git a/api_docs/kbn_managed_vscode_config.mdx b/api_docs/kbn_managed_vscode_config.mdx index a985901e4469e2..64dd616d28ddc5 100644 --- a/api_docs/kbn_managed_vscode_config.mdx +++ b/api_docs/kbn_managed_vscode_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-managed-vscode-config title: "@kbn/managed-vscode-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/managed-vscode-config plugin -date: 2023-11-06 +date: 2023-11-10 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 50cbc4d041bda5..f6a0840ccf9bfb 100644 --- a/api_docs/kbn_management_cards_navigation.mdx +++ b/api_docs/kbn_management_cards_navigation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-cards-navigation title: "@kbn/management-cards-navigation" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-cards-navigation plugin -date: 2023-11-06 +date: 2023-11-10 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 f953773df036df..68b3e804e6e356 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: 2023-11-06 +date: 2023-11-10 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 96955519772b02..1e8fb3e17c2b97 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: 2023-11-06 +date: 2023-11-10 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 a6bb7eaa7dd1f8..f7f0e50d492603 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: 2023-11-06 +date: 2023-11-10 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 44d6e19ea4eead..634dc16c0ca57e 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: 2023-11-06 +date: 2023-11-10 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 fb9610693c13ba..f2c8b1117eb524 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: 2023-11-06 +date: 2023-11-10 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 ddbaa9a509bcb4..bbaf9cfa3fd3d8 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: 2023-11-06 +date: 2023-11-10 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.mdx b/api_docs/kbn_management_settings_ids.mdx index 4d34e6e06cdc94..4362d975e2b02a 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: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-ids'] --- import kbnManagementSettingsIdsObj from './kbn_management_settings_ids.devdocs.json'; diff --git a/api_docs/kbn_management_settings_section_registry.mdx b/api_docs/kbn_management_settings_section_registry.mdx index b911b909385556..e3c5f1d14528f5 100644 --- a/api_docs/kbn_management_settings_section_registry.mdx +++ b/api_docs/kbn_management_settings_section_registry.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-section-registry title: "@kbn/management-settings-section-registry" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-section-registry plugin -date: 2023-11-06 +date: 2023-11-10 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 f83d1271b7be2c..32919445c9ada3 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: 2023-11-06 +date: 2023-11-10 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 fe4a66a94ee31c..18f31e9ba65844 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: 2023-11-06 +date: 2023-11-10 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 06e19845bbe6a2..be0b3dc7d5831b 100644 --- a/api_docs/kbn_management_storybook_config.mdx +++ b/api_docs/kbn_management_storybook_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-storybook-config title: "@kbn/management-storybook-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-storybook-config plugin -date: 2023-11-06 +date: 2023-11-10 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.devdocs.json b/api_docs/kbn_mapbox_gl.devdocs.json index 29866944ea667d..18b71846c478e4 100644 --- a/api_docs/kbn_mapbox_gl.devdocs.json +++ b/api_docs/kbn_mapbox_gl.devdocs.json @@ -18,6 +18,155 @@ }, "common": { "classes": [ + { + "parentPluginId": "@kbn/mapbox-gl", + "id": "def-common.AJAXError", + "type": "Class", + "tags": [], + "label": "AJAXError", + "description": [], + "signature": [ + "maplibregl.AJAXError extends Error" + ], + "path": "node_modules/maplibre-gl/dist/maplibre-gl.d.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/mapbox-gl", + "id": "def-common.AJAXError.status", + "type": "number", + "tags": [], + "label": "status", + "description": [ + "\nThe response's HTTP status code." + ], + "path": "node_modules/maplibre-gl/dist/maplibre-gl.d.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/mapbox-gl", + "id": "def-common.AJAXError.statusText", + "type": "string", + "tags": [], + "label": "statusText", + "description": [ + "\nThe response's HTTP status text." + ], + "path": "node_modules/maplibre-gl/dist/maplibre-gl.d.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/mapbox-gl", + "id": "def-common.AJAXError.url", + "type": "string", + "tags": [], + "label": "url", + "description": [ + "\nThe request's URL." + ], + "path": "node_modules/maplibre-gl/dist/maplibre-gl.d.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/mapbox-gl", + "id": "def-common.AJAXError.body", + "type": "Object", + "tags": [], + "label": "body", + "description": [ + "\nThe response's body." + ], + "signature": [ + "Blob" + ], + "path": "node_modules/maplibre-gl/dist/maplibre-gl.d.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/mapbox-gl", + "id": "def-common.AJAXError.Unnamed", + "type": "Function", + "tags": [], + "label": "Constructor", + "description": [], + "signature": [ + "any" + ], + "path": "node_modules/maplibre-gl/dist/maplibre-gl.d.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/mapbox-gl", + "id": "def-common.AJAXError.Unnamed.$1", + "type": "number", + "tags": [], + "label": "status", + "description": [], + "signature": [ + "number" + ], + "path": "node_modules/maplibre-gl/dist/maplibre-gl.d.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/mapbox-gl", + "id": "def-common.AJAXError.Unnamed.$2", + "type": "string", + "tags": [], + "label": "statusText", + "description": [], + "signature": [ + "string" + ], + "path": "node_modules/maplibre-gl/dist/maplibre-gl.d.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/mapbox-gl", + "id": "def-common.AJAXError.Unnamed.$3", + "type": "string", + "tags": [], + "label": "url", + "description": [], + "signature": [ + "string" + ], + "path": "node_modules/maplibre-gl/dist/maplibre-gl.d.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/mapbox-gl", + "id": "def-common.AJAXError.Unnamed.$4", + "type": "Object", + "tags": [], + "label": "body", + "description": [], + "signature": [ + "Blob" + ], + "path": "node_modules/maplibre-gl/dist/maplibre-gl.d.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/mapbox-gl", "id": "def-common.GeoJSONSource", diff --git a/api_docs/kbn_mapbox_gl.mdx b/api_docs/kbn_mapbox_gl.mdx index a3dc6067d159a4..dc43329b36e4d9 100644 --- a/api_docs/kbn_mapbox_gl.mdx +++ b/api_docs/kbn_mapbox_gl.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-mapbox-gl title: "@kbn/mapbox-gl" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/mapbox-gl plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/mapbox-gl'] --- import kbnMapboxGlObj from './kbn_mapbox_gl.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-gis](https://github.com/orgs/elastic/teams/kibana-gis) | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 582 | 1 | 1 | 0 | +| 592 | 1 | 1 | 0 | ## Common diff --git a/api_docs/kbn_maps_vector_tile_utils.mdx b/api_docs/kbn_maps_vector_tile_utils.mdx index 6822eeaba58eb9..2393fa9c52e42f 100644 --- a/api_docs/kbn_maps_vector_tile_utils.mdx +++ b/api_docs/kbn_maps_vector_tile_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-maps-vector-tile-utils title: "@kbn/maps-vector-tile-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/maps-vector-tile-utils plugin -date: 2023-11-06 +date: 2023-11-10 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 8433d17aa9324c..bd992efab920d7 100644 --- a/api_docs/kbn_ml_agg_utils.mdx +++ b/api_docs/kbn_ml_agg_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-agg-utils title: "@kbn/ml-agg-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-agg-utils plugin -date: 2023-11-06 +date: 2023-11-10 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 dd4c3ded898b2e..55f2e0a8ec191d 100644 --- a/api_docs/kbn_ml_anomaly_utils.mdx +++ b/api_docs/kbn_ml_anomaly_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-anomaly-utils title: "@kbn/ml-anomaly-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-anomaly-utils plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-anomaly-utils'] --- import kbnMlAnomalyUtilsObj from './kbn_ml_anomaly_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_category_validator.mdx b/api_docs/kbn_ml_category_validator.mdx index 3be29f042ab281..a8d1ad72fc6159 100644 --- a/api_docs/kbn_ml_category_validator.mdx +++ b/api_docs/kbn_ml_category_validator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-category-validator title: "@kbn/ml-category-validator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-category-validator plugin -date: 2023-11-06 +date: 2023-11-10 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 46c385f3901dd1..a44266c0f9a626 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: 2023-11-06 +date: 2023-11-10 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 e8025edf583b51..1b24bb55f21a18 100644 --- a/api_docs/kbn_ml_data_frame_analytics_utils.mdx +++ b/api_docs/kbn_ml_data_frame_analytics_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-data-frame-analytics-utils title: "@kbn/ml-data-frame-analytics-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-data-frame-analytics-utils plugin -date: 2023-11-06 +date: 2023-11-10 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 08de26b56c63c1..676f7d5cf674e2 100644 --- a/api_docs/kbn_ml_data_grid.mdx +++ b/api_docs/kbn_ml_data_grid.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-data-grid title: "@kbn/ml-data-grid" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-data-grid plugin -date: 2023-11-06 +date: 2023-11-10 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 528f48522d1346..a335e71c63b845 100644 --- a/api_docs/kbn_ml_date_picker.mdx +++ b/api_docs/kbn_ml_date_picker.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-date-picker title: "@kbn/ml-date-picker" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-date-picker plugin -date: 2023-11-06 +date: 2023-11-10 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 712c78d15cec09..3aaa696572ec09 100644 --- a/api_docs/kbn_ml_date_utils.mdx +++ b/api_docs/kbn_ml_date_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-date-utils title: "@kbn/ml-date-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-date-utils plugin -date: 2023-11-06 +date: 2023-11-10 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 c3553d60b7c58f..c591337628e1ed 100644 --- a/api_docs/kbn_ml_error_utils.mdx +++ b/api_docs/kbn_ml_error_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-error-utils title: "@kbn/ml-error-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-error-utils plugin -date: 2023-11-06 +date: 2023-11-10 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 2d04782c6a1e99..541335f302c89d 100644 --- a/api_docs/kbn_ml_in_memory_table.mdx +++ b/api_docs/kbn_ml_in_memory_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-in-memory-table title: "@kbn/ml-in-memory-table" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-in-memory-table plugin -date: 2023-11-06 +date: 2023-11-10 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 2b7fd3a5ae0b39..d52bbbf3cfc6d1 100644 --- a/api_docs/kbn_ml_is_defined.mdx +++ b/api_docs/kbn_ml_is_defined.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-is-defined title: "@kbn/ml-is-defined" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-is-defined plugin -date: 2023-11-06 +date: 2023-11-10 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 4b1f73b21d199b..3a2a11db262c27 100644 --- a/api_docs/kbn_ml_is_populated_object.mdx +++ b/api_docs/kbn_ml_is_populated_object.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-is-populated-object title: "@kbn/ml-is-populated-object" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-is-populated-object plugin -date: 2023-11-06 +date: 2023-11-10 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 8bd2e53ed6d39a..70ebaf2191303b 100644 --- a/api_docs/kbn_ml_kibana_theme.mdx +++ b/api_docs/kbn_ml_kibana_theme.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-kibana-theme title: "@kbn/ml-kibana-theme" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-kibana-theme plugin -date: 2023-11-06 +date: 2023-11-10 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 eefffa4ad27c4a..16b14fc2224ec6 100644 --- a/api_docs/kbn_ml_local_storage.mdx +++ b/api_docs/kbn_ml_local_storage.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-local-storage title: "@kbn/ml-local-storage" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-local-storage plugin -date: 2023-11-06 +date: 2023-11-10 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 43175eb46c336e..a7229680877cec 100644 --- a/api_docs/kbn_ml_nested_property.mdx +++ b/api_docs/kbn_ml_nested_property.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-nested-property title: "@kbn/ml-nested-property" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-nested-property plugin -date: 2023-11-06 +date: 2023-11-10 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 46d61785cd582e..bac5531a272e42 100644 --- a/api_docs/kbn_ml_number_utils.mdx +++ b/api_docs/kbn_ml_number_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-number-utils title: "@kbn/ml-number-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-number-utils plugin -date: 2023-11-06 +date: 2023-11-10 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 b0a8ccb675aac9..023a1b30198f17 100644 --- a/api_docs/kbn_ml_query_utils.mdx +++ b/api_docs/kbn_ml_query_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-query-utils title: "@kbn/ml-query-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-query-utils plugin -date: 2023-11-06 +date: 2023-11-10 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 9b4d089a263cb0..2d5dac3d224775 100644 --- a/api_docs/kbn_ml_random_sampler_utils.mdx +++ b/api_docs/kbn_ml_random_sampler_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-random-sampler-utils title: "@kbn/ml-random-sampler-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-random-sampler-utils plugin -date: 2023-11-06 +date: 2023-11-10 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 8280fa14649fc3..ab284617e97364 100644 --- a/api_docs/kbn_ml_route_utils.mdx +++ b/api_docs/kbn_ml_route_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-route-utils title: "@kbn/ml-route-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-route-utils plugin -date: 2023-11-06 +date: 2023-11-10 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 06864edf0eeb25..ca8be667ad077d 100644 --- a/api_docs/kbn_ml_runtime_field_utils.mdx +++ b/api_docs/kbn_ml_runtime_field_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-runtime-field-utils title: "@kbn/ml-runtime-field-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-runtime-field-utils plugin -date: 2023-11-06 +date: 2023-11-10 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 bb9d8b0434ab35..e9aa70d1342981 100644 --- a/api_docs/kbn_ml_string_hash.mdx +++ b/api_docs/kbn_ml_string_hash.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-string-hash title: "@kbn/ml-string-hash" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-string-hash plugin -date: 2023-11-06 +date: 2023-11-10 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 f77e3efcd509ca..dcc416aefcd296 100644 --- a/api_docs/kbn_ml_trained_models_utils.mdx +++ b/api_docs/kbn_ml_trained_models_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-trained-models-utils title: "@kbn/ml-trained-models-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-trained-models-utils plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-trained-models-utils'] --- import kbnMlTrainedModelsUtilsObj from './kbn_ml_trained_models_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_url_state.mdx b/api_docs/kbn_ml_url_state.mdx index 8941758fa9e3d3..d9326f42d3ab31 100644 --- a/api_docs/kbn_ml_url_state.mdx +++ b/api_docs/kbn_ml_url_state.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-url-state title: "@kbn/ml-url-state" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-url-state plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-url-state'] --- import kbnMlUrlStateObj from './kbn_ml_url_state.devdocs.json'; diff --git a/api_docs/kbn_monaco.mdx b/api_docs/kbn_monaco.mdx index 6e1af717929097..50bb7fd91029ff 100644 --- a/api_docs/kbn_monaco.mdx +++ b/api_docs/kbn_monaco.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-monaco title: "@kbn/monaco" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/monaco plugin -date: 2023-11-06 +date: 2023-11-10 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 0f4cc0b1bdd500..adff97b496ef41 100644 --- a/api_docs/kbn_object_versioning.mdx +++ b/api_docs/kbn_object_versioning.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-object-versioning title: "@kbn/object-versioning" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/object-versioning plugin -date: 2023-11-06 +date: 2023-11-10 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 b986c3238cf8c4..8be4d3ec13e90b 100644 --- a/api_docs/kbn_observability_alert_details.mdx +++ b/api_docs/kbn_observability_alert_details.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-observability-alert-details title: "@kbn/observability-alert-details" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/observability-alert-details plugin -date: 2023-11-06 +date: 2023-11-10 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 be1e0be15b3aef..4f4dcef65b1b04 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: 2023-11-06 +date: 2023-11-10 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_openapi_generator.mdx b/api_docs/kbn_openapi_generator.mdx index e9eb1d74015a57..1fb54e33a05442 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: 2023-11-06 +date: 2023-11-10 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 af7523fb57040b..fb50d3d5303468 100644 --- a/api_docs/kbn_optimizer.mdx +++ b/api_docs/kbn_optimizer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-optimizer title: "@kbn/optimizer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/optimizer plugin -date: 2023-11-06 +date: 2023-11-10 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 0430c65855dfe0..2de86998a5a02d 100644 --- a/api_docs/kbn_optimizer_webpack_helpers.mdx +++ b/api_docs/kbn_optimizer_webpack_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-optimizer-webpack-helpers title: "@kbn/optimizer-webpack-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/optimizer-webpack-helpers plugin -date: 2023-11-06 +date: 2023-11-10 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 369c600e159982..be21d9b5803635 100644 --- a/api_docs/kbn_osquery_io_ts_types.mdx +++ b/api_docs/kbn_osquery_io_ts_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-osquery-io-ts-types title: "@kbn/osquery-io-ts-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/osquery-io-ts-types plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/osquery-io-ts-types'] --- import kbnOsqueryIoTsTypesObj from './kbn_osquery_io_ts_types.devdocs.json'; diff --git a/api_docs/kbn_performance_testing_dataset_extractor.mdx b/api_docs/kbn_performance_testing_dataset_extractor.mdx index f089c4ad44cb55..31b082132154b2 100644 --- a/api_docs/kbn_performance_testing_dataset_extractor.mdx +++ b/api_docs/kbn_performance_testing_dataset_extractor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-performance-testing-dataset-extractor title: "@kbn/performance-testing-dataset-extractor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/performance-testing-dataset-extractor plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/performance-testing-dataset-extractor'] --- import kbnPerformanceTestingDatasetExtractorObj from './kbn_performance_testing_dataset_extractor.devdocs.json'; diff --git a/api_docs/kbn_plugin_generator.mdx b/api_docs/kbn_plugin_generator.mdx index b9273df4515add..a0adee613bde5e 100644 --- a/api_docs/kbn_plugin_generator.mdx +++ b/api_docs/kbn_plugin_generator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-plugin-generator title: "@kbn/plugin-generator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/plugin-generator plugin -date: 2023-11-06 +date: 2023-11-10 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 9b4acb55119ed9..3e310269ff7a56 100644 --- a/api_docs/kbn_plugin_helpers.mdx +++ b/api_docs/kbn_plugin_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-plugin-helpers title: "@kbn/plugin-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/plugin-helpers plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-helpers'] --- import kbnPluginHelpersObj from './kbn_plugin_helpers.devdocs.json'; diff --git a/api_docs/kbn_profiling_utils.mdx b/api_docs/kbn_profiling_utils.mdx index b1477307940e8b..7909bb48bfb9ce 100644 --- a/api_docs/kbn_profiling_utils.mdx +++ b/api_docs/kbn_profiling_utils.mdx @@ -8,14 +8,14 @@ slug: /kibana-dev-docs/api/kbn-profiling-utils title: "@kbn/profiling-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/profiling-utils plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/profiling-utils'] --- import kbnProfilingUtilsObj from './kbn_profiling_utils.devdocs.json'; -Contact [@elastic/profiling-ui](https://github.com/orgs/elastic/teams/profiling-ui) for questions regarding this plugin. +Contact [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) for questions regarding this plugin. **Code health stats** diff --git a/api_docs/kbn_random_sampling.mdx b/api_docs/kbn_random_sampling.mdx index 8d8fa6fae40c1a..13b5ef7fe02b31 100644 --- a/api_docs/kbn_random_sampling.mdx +++ b/api_docs/kbn_random_sampling.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-random-sampling title: "@kbn/random-sampling" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/random-sampling plugin -date: 2023-11-06 +date: 2023-11-10 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 3f170b249f7dfb..f3a6766d9463c1 100644 --- a/api_docs/kbn_react_field.mdx +++ b/api_docs/kbn_react_field.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-field title: "@kbn/react-field" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-field plugin -date: 2023-11-06 +date: 2023-11-10 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 40c6bc649547e3..37a673eae009e0 100644 --- a/api_docs/kbn_react_kibana_context_common.mdx +++ b/api_docs/kbn_react_kibana_context_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-kibana-context-common title: "@kbn/react-kibana-context-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-kibana-context-common plugin -date: 2023-11-06 +date: 2023-11-10 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 85685543307e5c..8ad0ff5e2160dd 100644 --- a/api_docs/kbn_react_kibana_context_render.mdx +++ b/api_docs/kbn_react_kibana_context_render.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-kibana-context-render title: "@kbn/react-kibana-context-render" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-kibana-context-render plugin -date: 2023-11-06 +date: 2023-11-10 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 e450e567950a97..f10e288cb222dc 100644 --- a/api_docs/kbn_react_kibana_context_root.mdx +++ b/api_docs/kbn_react_kibana_context_root.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-kibana-context-root title: "@kbn/react-kibana-context-root" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-kibana-context-root plugin -date: 2023-11-06 +date: 2023-11-10 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 2a3a7f5a9761db..73705ad455a5cf 100644 --- a/api_docs/kbn_react_kibana_context_styled.mdx +++ b/api_docs/kbn_react_kibana_context_styled.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-kibana-context-styled title: "@kbn/react-kibana-context-styled" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-kibana-context-styled plugin -date: 2023-11-06 +date: 2023-11-10 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 aeba5a58f5d546..5a1e5cf59dfb8f 100644 --- a/api_docs/kbn_react_kibana_context_theme.mdx +++ b/api_docs/kbn_react_kibana_context_theme.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-kibana-context-theme title: "@kbn/react-kibana-context-theme" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-kibana-context-theme plugin -date: 2023-11-06 +date: 2023-11-10 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 429a2bc5322fbb..6672d274d7393f 100644 --- a/api_docs/kbn_react_kibana_mount.mdx +++ b/api_docs/kbn_react_kibana_mount.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-kibana-mount title: "@kbn/react-kibana-mount" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-kibana-mount plugin -date: 2023-11-06 +date: 2023-11-10 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 5c0f40d44c809f..0d340875a75f89 100644 --- a/api_docs/kbn_repo_file_maps.mdx +++ b/api_docs/kbn_repo_file_maps.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-file-maps title: "@kbn/repo-file-maps" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-file-maps plugin -date: 2023-11-06 +date: 2023-11-10 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 5e8efd2df09e7c..8856e455244044 100644 --- a/api_docs/kbn_repo_linter.mdx +++ b/api_docs/kbn_repo_linter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-linter title: "@kbn/repo-linter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-linter plugin -date: 2023-11-06 +date: 2023-11-10 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 a89bd68c9523d8..67879dd6e0765e 100644 --- a/api_docs/kbn_repo_path.mdx +++ b/api_docs/kbn_repo_path.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-path title: "@kbn/repo-path" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-path plugin -date: 2023-11-06 +date: 2023-11-10 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 c8ad5590f14caa..ce39596ea601c6 100644 --- a/api_docs/kbn_repo_source_classifier.mdx +++ b/api_docs/kbn_repo_source_classifier.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-source-classifier title: "@kbn/repo-source-classifier" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-source-classifier plugin -date: 2023-11-06 +date: 2023-11-10 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 514eb199bc6847..b5c319501040a7 100644 --- a/api_docs/kbn_reporting_common.mdx +++ b/api_docs/kbn_reporting_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-common title: "@kbn/reporting-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-common plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-common'] --- import kbnReportingCommonObj from './kbn_reporting_common.devdocs.json'; diff --git a/api_docs/kbn_resizable_layout.mdx b/api_docs/kbn_resizable_layout.mdx index 5574a45c3d936e..ce646a218bec63 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: 2023-11-06 +date: 2023-11-10 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 efd92dc677b37e..6675f7b7a2676c 100644 --- a/api_docs/kbn_rison.mdx +++ b/api_docs/kbn_rison.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-rison title: "@kbn/rison" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/rison plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rison'] --- import kbnRisonObj from './kbn_rison.devdocs.json'; diff --git a/api_docs/kbn_rrule.mdx b/api_docs/kbn_rrule.mdx index 4cf7369b7053e6..abc9f781b2569d 100644 --- a/api_docs/kbn_rrule.mdx +++ b/api_docs/kbn_rrule.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-rrule title: "@kbn/rrule" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/rrule plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rrule'] --- import kbnRruleObj from './kbn_rrule.devdocs.json'; diff --git a/api_docs/kbn_rule_data_utils.devdocs.json b/api_docs/kbn_rule_data_utils.devdocs.json index 20def9bb105f62..f721e756a60e20 100644 --- a/api_docs/kbn_rule_data_utils.devdocs.json +++ b/api_docs/kbn_rule_data_utils.devdocs.json @@ -1586,6 +1586,21 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/rule-data-utils", + "id": "def-common.METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID", + "type": "string", + "tags": [], + "label": "METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID", + "description": [], + "signature": [ + "\"metrics.alert.inventory.threshold\"" + ], + "path": "packages/kbn-rule-data-utils/src/rule_types/o11y_rules.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/rule-data-utils", "id": "def-common.OBSERVABILITY_THRESHOLD_RULE_TYPE_ID", diff --git a/api_docs/kbn_rule_data_utils.mdx b/api_docs/kbn_rule_data_utils.mdx index 0e342025213629..57322a84712a52 100644 --- a/api_docs/kbn_rule_data_utils.mdx +++ b/api_docs/kbn_rule_data_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-rule-data-utils title: "@kbn/rule-data-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/rule-data-utils plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rule-data-utils'] --- import kbnRuleDataUtilsObj from './kbn_rule_data_utils.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/security-detections-response](https://github.com/orgs/elastic/ | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 115 | 0 | 112 | 0 | +| 116 | 0 | 113 | 0 | ## Common diff --git a/api_docs/kbn_saved_objects_settings.mdx b/api_docs/kbn_saved_objects_settings.mdx index 197900c046efca..f8812a5f99a073 100644 --- a/api_docs/kbn_saved_objects_settings.mdx +++ b/api_docs/kbn_saved_objects_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-saved-objects-settings title: "@kbn/saved-objects-settings" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/saved-objects-settings plugin -date: 2023-11-06 +date: 2023-11-10 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 7273f8b03de58d..4d5bb7c6ee285a 100644 --- a/api_docs/kbn_search_api_panels.mdx +++ b/api_docs/kbn_search_api_panels.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-search-api-panels title: "@kbn/search-api-panels" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/search-api-panels plugin -date: 2023-11-06 +date: 2023-11-10 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.mdx b/api_docs/kbn_search_connectors.mdx index 330a77422c248c..b54f6751fc94ff 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: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-connectors'] --- import kbnSearchConnectorsObj from './kbn_search_connectors.devdocs.json'; diff --git a/api_docs/kbn_search_response_warnings.mdx b/api_docs/kbn_search_response_warnings.mdx index 787b9139b4369d..717727a6f8abac 100644 --- a/api_docs/kbn_search_response_warnings.mdx +++ b/api_docs/kbn_search_response_warnings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-search-response-warnings title: "@kbn/search-response-warnings" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/search-response-warnings plugin -date: 2023-11-06 +date: 2023-11-10 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_solution_features.mdx b/api_docs/kbn_security_solution_features.mdx index e72d3f39e24df5..40f2ec9326b91d 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: 2023-11-06 +date: 2023-11-10 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 6ff2dc35f5ce4f..f1ea786251d028 100644 --- a/api_docs/kbn_security_solution_navigation.mdx +++ b/api_docs/kbn_security_solution_navigation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-solution-navigation title: "@kbn/security-solution-navigation" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-solution-navigation plugin -date: 2023-11-06 +date: 2023-11-10 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 dc2796286112ee..f6a85eb1b22b08 100644 --- a/api_docs/kbn_security_solution_side_nav.mdx +++ b/api_docs/kbn_security_solution_side_nav.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-solution-side-nav title: "@kbn/security-solution-side-nav" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-solution-side-nav plugin -date: 2023-11-06 +date: 2023-11-10 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 c0e16021c6df6b..c2a939ccbcad79 100644 --- a/api_docs/kbn_security_solution_storybook_config.mdx +++ b/api_docs/kbn_security_solution_storybook_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-solution-storybook-config title: "@kbn/security-solution-storybook-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-solution-storybook-config plugin -date: 2023-11-06 +date: 2023-11-10 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 027b6e8ee6de10..ffdcdf056987a2 100644 --- a/api_docs/kbn_securitysolution_autocomplete.mdx +++ b/api_docs/kbn_securitysolution_autocomplete.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-autocomplete title: "@kbn/securitysolution-autocomplete" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-autocomplete plugin -date: 2023-11-06 +date: 2023-11-10 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 fb0c7606025060..1190ca76448213 100644 --- a/api_docs/kbn_securitysolution_data_table.mdx +++ b/api_docs/kbn_securitysolution_data_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-data-table title: "@kbn/securitysolution-data-table" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-data-table plugin -date: 2023-11-06 +date: 2023-11-10 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 de59f001b2fc20..0cbeffbcdff275 100644 --- a/api_docs/kbn_securitysolution_ecs.mdx +++ b/api_docs/kbn_securitysolution_ecs.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-ecs title: "@kbn/securitysolution-ecs" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-ecs plugin -date: 2023-11-06 +date: 2023-11-10 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 25a84212e1f41a..323c13e48fb534 100644 --- a/api_docs/kbn_securitysolution_es_utils.mdx +++ b/api_docs/kbn_securitysolution_es_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-es-utils title: "@kbn/securitysolution-es-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-es-utils plugin -date: 2023-11-06 +date: 2023-11-10 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 379ec234cfc481..1c7d1357c04498 100644 --- a/api_docs/kbn_securitysolution_exception_list_components.mdx +++ b/api_docs/kbn_securitysolution_exception_list_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-exception-list-components title: "@kbn/securitysolution-exception-list-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-exception-list-components plugin -date: 2023-11-06 +date: 2023-11-10 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 d30e31944bc09a..35b71a8f9af684 100644 --- a/api_docs/kbn_securitysolution_grouping.mdx +++ b/api_docs/kbn_securitysolution_grouping.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-grouping title: "@kbn/securitysolution-grouping" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-grouping plugin -date: 2023-11-06 +date: 2023-11-10 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 ab51b6cb1912fe..77957415ef3ed2 100644 --- a/api_docs/kbn_securitysolution_hook_utils.mdx +++ b/api_docs/kbn_securitysolution_hook_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-hook-utils title: "@kbn/securitysolution-hook-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-hook-utils plugin -date: 2023-11-06 +date: 2023-11-10 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 8c9c2e30a45785..3f48ae66fbb0e9 100644 --- a/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-alerting-types title: "@kbn/securitysolution-io-ts-alerting-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-alerting-types plugin -date: 2023-11-06 +date: 2023-11-10 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 013f582332fd75..918cb773c4e876 100644 --- a/api_docs/kbn_securitysolution_io_ts_list_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_list_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-list-types title: "@kbn/securitysolution-io-ts-list-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-list-types plugin -date: 2023-11-06 +date: 2023-11-10 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 58ebcc56a6d415..6e30d2c48b41e4 100644 --- a/api_docs/kbn_securitysolution_io_ts_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-types title: "@kbn/securitysolution-io-ts-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-types plugin -date: 2023-11-06 +date: 2023-11-10 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 89588158fec889..246a043063f7f8 100644 --- a/api_docs/kbn_securitysolution_io_ts_utils.mdx +++ b/api_docs/kbn_securitysolution_io_ts_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-utils title: "@kbn/securitysolution-io-ts-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-utils plugin -date: 2023-11-06 +date: 2023-11-10 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 7ce52f197727c3..4e488db5b32907 100644 --- a/api_docs/kbn_securitysolution_list_api.mdx +++ b/api_docs/kbn_securitysolution_list_api.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-api title: "@kbn/securitysolution-list-api" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-api plugin -date: 2023-11-06 +date: 2023-11-10 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 321b34fc65a3d6..84080bd4212369 100644 --- a/api_docs/kbn_securitysolution_list_constants.mdx +++ b/api_docs/kbn_securitysolution_list_constants.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-constants title: "@kbn/securitysolution-list-constants" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-constants plugin -date: 2023-11-06 +date: 2023-11-10 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 deb61a8436c01c..31d83b7b417784 100644 --- a/api_docs/kbn_securitysolution_list_hooks.mdx +++ b/api_docs/kbn_securitysolution_list_hooks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-hooks title: "@kbn/securitysolution-list-hooks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-hooks plugin -date: 2023-11-06 +date: 2023-11-10 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 3d335bb199b1ee..512c6a09d094b9 100644 --- a/api_docs/kbn_securitysolution_list_utils.mdx +++ b/api_docs/kbn_securitysolution_list_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-utils title: "@kbn/securitysolution-list-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-utils plugin -date: 2023-11-06 +date: 2023-11-10 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 94d8661abb44b8..719cba8a9b6d0e 100644 --- a/api_docs/kbn_securitysolution_rules.mdx +++ b/api_docs/kbn_securitysolution_rules.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-rules title: "@kbn/securitysolution-rules" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-rules plugin -date: 2023-11-06 +date: 2023-11-10 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 6a33dacd5ba281..4e03cdf5b73c08 100644 --- a/api_docs/kbn_securitysolution_t_grid.mdx +++ b/api_docs/kbn_securitysolution_t_grid.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-t-grid title: "@kbn/securitysolution-t-grid" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-t-grid plugin -date: 2023-11-06 +date: 2023-11-10 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 7a5f22885a026d..c8457990219b93 100644 --- a/api_docs/kbn_securitysolution_utils.mdx +++ b/api_docs/kbn_securitysolution_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-utils title: "@kbn/securitysolution-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-utils plugin -date: 2023-11-06 +date: 2023-11-10 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 f54d3f67705663..e9b483fb891803 100644 --- a/api_docs/kbn_server_http_tools.mdx +++ b/api_docs/kbn_server_http_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-server-http-tools title: "@kbn/server-http-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/server-http-tools plugin -date: 2023-11-06 +date: 2023-11-10 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 a3c64eb64781ed..e3b44fd839da4a 100644 --- a/api_docs/kbn_server_route_repository.mdx +++ b/api_docs/kbn_server_route_repository.mdx @@ -8,14 +8,14 @@ slug: /kibana-dev-docs/api/kbn-server-route-repository title: "@kbn/server-route-repository" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/server-route-repository plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/server-route-repository'] --- import kbnServerRouteRepositoryObj from './kbn_server_route_repository.devdocs.json'; -Contact [@elastic/apm-ui](https://github.com/orgs/elastic/teams/apm-ui) for questions regarding this plugin. +Contact [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs-knowledge-team) for questions regarding this plugin. **Code health stats** diff --git a/api_docs/kbn_serverless_common_settings.mdx b/api_docs/kbn_serverless_common_settings.mdx index 34d5c3e0891529..db838d30b6ba31 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: 2023-11-06 +date: 2023-11-10 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 786d674572f6a0..819abbf3430c16 100644 --- a/api_docs/kbn_serverless_observability_settings.mdx +++ b/api_docs/kbn_serverless_observability_settings.mdx @@ -8,14 +8,14 @@ slug: /kibana-dev-docs/api/kbn-serverless-observability-settings title: "@kbn/serverless-observability-settings" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/serverless-observability-settings plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/serverless-observability-settings'] --- import kbnServerlessObservabilitySettingsObj from './kbn_serverless_observability_settings.devdocs.json'; -Contact [@elastic/appex-sharedux @elastic/apm-ui @elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/appex-sharedux ) for questions regarding this plugin. +Contact [@elastic/appex-sharedux @elastic/platform-deployment-management @elastic/obs-ux-management-team](https://github.com/orgs/elastic/teams/appex-sharedux ) for questions regarding this plugin. **Code health stats** diff --git a/api_docs/kbn_serverless_project_switcher.mdx b/api_docs/kbn_serverless_project_switcher.mdx index a2645df15ec018..25ade2bc4c5ff4 100644 --- a/api_docs/kbn_serverless_project_switcher.mdx +++ b/api_docs/kbn_serverless_project_switcher.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-serverless-project-switcher title: "@kbn/serverless-project-switcher" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/serverless-project-switcher plugin -date: 2023-11-06 +date: 2023-11-10 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 7bb2bd8c476c39..1f12da911ce273 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: 2023-11-06 +date: 2023-11-10 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 854e8229c06569..a55c03c341224c 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: 2023-11-06 +date: 2023-11-10 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 08e3fd74a66b8f..d0a65c9583006d 100644 --- a/api_docs/kbn_serverless_storybook_config.mdx +++ b/api_docs/kbn_serverless_storybook_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-serverless-storybook-config title: "@kbn/serverless-storybook-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/serverless-storybook-config plugin -date: 2023-11-06 +date: 2023-11-10 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 34685b91bc2521..7e629bb13419c3 100644 --- a/api_docs/kbn_shared_svg.mdx +++ b/api_docs/kbn_shared_svg.mdx @@ -8,14 +8,14 @@ slug: /kibana-dev-docs/api/kbn-shared-svg title: "@kbn/shared-svg" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-svg plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-svg'] --- import kbnSharedSvgObj from './kbn_shared_svg.devdocs.json'; -Contact [@elastic/apm-ui](https://github.com/orgs/elastic/teams/apm-ui) for questions regarding this plugin. +Contact [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) for questions regarding this plugin. **Code health stats** diff --git a/api_docs/kbn_shared_ux_avatar_solution.mdx b/api_docs/kbn_shared_ux_avatar_solution.mdx index d3a0bcc81012b5..8a53913f9f7e75 100644 --- a/api_docs/kbn_shared_ux_avatar_solution.mdx +++ b/api_docs/kbn_shared_ux_avatar_solution.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-avatar-solution title: "@kbn/shared-ux-avatar-solution" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-avatar-solution plugin -date: 2023-11-06 +date: 2023-11-10 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 0b71e4d7450095..d085adbf782b61 100644 --- a/api_docs/kbn_shared_ux_button_exit_full_screen.mdx +++ b/api_docs/kbn_shared_ux_button_exit_full_screen.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-button-exit-full-screen title: "@kbn/shared-ux-button-exit-full-screen" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-button-exit-full-screen plugin -date: 2023-11-06 +date: 2023-11-10 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 002bc96a3ed2bf..4160c23c498640 100644 --- a/api_docs/kbn_shared_ux_button_toolbar.mdx +++ b/api_docs/kbn_shared_ux_button_toolbar.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-button-toolbar title: "@kbn/shared-ux-button-toolbar" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-button-toolbar plugin -date: 2023-11-06 +date: 2023-11-10 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 a43c9ca971e1be..2abf35b050f9db 100644 --- a/api_docs/kbn_shared_ux_card_no_data.mdx +++ b/api_docs/kbn_shared_ux_card_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-card-no-data title: "@kbn/shared-ux-card-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-card-no-data plugin -date: 2023-11-06 +date: 2023-11-10 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 c2c1964cbd9342..358782c8b70055 100644 --- a/api_docs/kbn_shared_ux_card_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_card_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-card-no-data-mocks title: "@kbn/shared-ux-card-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-card-no-data-mocks plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-card-no-data-mocks'] --- import kbnSharedUxCardNoDataMocksObj from './kbn_shared_ux_card_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_chrome_navigation.devdocs.json b/api_docs/kbn_shared_ux_chrome_navigation.devdocs.json index c3b285c9899c71..41742443ed3bf3 100644 --- a/api_docs/kbn_shared_ux_chrome_navigation.devdocs.json +++ b/api_docs/kbn_shared_ux_chrome_navigation.devdocs.json @@ -871,7 +871,15 @@ "label": "cloudLinks", "description": [], "signature": [ - "{ userAndRoles?: { title: string; href: string; } | undefined; performance?: { title: string; href: string; } | undefined; billingAndSub?: { title: string; href: string; } | undefined; deployment?: { title: string; href: string; } | undefined; }" + "{ userAndRoles?: ", + "CloudLink", + " | undefined; performance?: ", + "CloudLink", + " | undefined; billingAndSub?: ", + "CloudLink", + " | undefined; deployment?: ", + "CloudLink", + " | undefined; }" ], "path": "packages/shared-ux/chrome/navigation/types/index.ts", "deprecated": false, diff --git a/api_docs/kbn_shared_ux_chrome_navigation.mdx b/api_docs/kbn_shared_ux_chrome_navigation.mdx index 639d45db1f760e..f9ecbe1644a963 100644 --- a/api_docs/kbn_shared_ux_chrome_navigation.mdx +++ b/api_docs/kbn_shared_ux_chrome_navigation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-chrome-navigation title: "@kbn/shared-ux-chrome-navigation" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-chrome-navigation plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-chrome-navigation'] --- import kbnSharedUxChromeNavigationObj from './kbn_shared_ux_chrome_navigation.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sh | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 60 | 0 | 49 | 4 | +| 60 | 0 | 49 | 5 | ## Common diff --git a/api_docs/kbn_shared_ux_error_boundary.mdx b/api_docs/kbn_shared_ux_error_boundary.mdx index 79f546e0bad79e..8e4e4c25d8f09a 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: 2023-11-06 +date: 2023-11-10 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 7d3c81214c6e75..3c539c58424ff2 100644 --- a/api_docs/kbn_shared_ux_file_context.mdx +++ b/api_docs/kbn_shared_ux_file_context.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-context title: "@kbn/shared-ux-file-context" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-context plugin -date: 2023-11-06 +date: 2023-11-10 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 faea7bda8c7bb1..b132bde96a8e37 100644 --- a/api_docs/kbn_shared_ux_file_image.mdx +++ b/api_docs/kbn_shared_ux_file_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-image title: "@kbn/shared-ux-file-image" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-image plugin -date: 2023-11-06 +date: 2023-11-10 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 0c56302682f8ea..f87049da346509 100644 --- a/api_docs/kbn_shared_ux_file_image_mocks.mdx +++ b/api_docs/kbn_shared_ux_file_image_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-image-mocks title: "@kbn/shared-ux-file-image-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-image-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 60502e2e3320ee..80ab3fa803ebe0 100644 --- a/api_docs/kbn_shared_ux_file_mocks.mdx +++ b/api_docs/kbn_shared_ux_file_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-mocks title: "@kbn/shared-ux-file-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 4ed0fe82a5ce42..0133656dffbad6 100644 --- a/api_docs/kbn_shared_ux_file_picker.mdx +++ b/api_docs/kbn_shared_ux_file_picker.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-picker title: "@kbn/shared-ux-file-picker" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-picker plugin -date: 2023-11-06 +date: 2023-11-10 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 671d551222f3dc..f05164714efb5c 100644 --- a/api_docs/kbn_shared_ux_file_types.mdx +++ b/api_docs/kbn_shared_ux_file_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-types title: "@kbn/shared-ux-file-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-types plugin -date: 2023-11-06 +date: 2023-11-10 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 4a68fd18af4b33..df743198d5eeb5 100644 --- a/api_docs/kbn_shared_ux_file_upload.mdx +++ b/api_docs/kbn_shared_ux_file_upload.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-upload title: "@kbn/shared-ux-file-upload" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-upload plugin -date: 2023-11-06 +date: 2023-11-10 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 ea116b7fc0a079..2dc3ad597bde21 100644 --- a/api_docs/kbn_shared_ux_file_util.mdx +++ b/api_docs/kbn_shared_ux_file_util.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-util title: "@kbn/shared-ux-file-util" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-util plugin -date: 2023-11-06 +date: 2023-11-10 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 8643212421d549..533dda0f6af2dd 100644 --- a/api_docs/kbn_shared_ux_link_redirect_app.mdx +++ b/api_docs/kbn_shared_ux_link_redirect_app.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-link-redirect-app title: "@kbn/shared-ux-link-redirect-app" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-link-redirect-app plugin -date: 2023-11-06 +date: 2023-11-10 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 823e1e052c13e6..482c09f26df625 100644 --- a/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx +++ b/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-link-redirect-app-mocks title: "@kbn/shared-ux-link-redirect-app-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-link-redirect-app-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 e2c2f5594ff718..13549825dc01f7 100644 --- a/api_docs/kbn_shared_ux_markdown.mdx +++ b/api_docs/kbn_shared_ux_markdown.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-markdown title: "@kbn/shared-ux-markdown" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-markdown plugin -date: 2023-11-06 +date: 2023-11-10 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 b218c7e2b1f747..6a4b750516e037 100644 --- a/api_docs/kbn_shared_ux_markdown_mocks.mdx +++ b/api_docs/kbn_shared_ux_markdown_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-markdown-mocks title: "@kbn/shared-ux-markdown-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-markdown-mocks plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-markdown-mocks'] --- import kbnSharedUxMarkdownMocksObj from './kbn_shared_ux_markdown_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_analytics_no_data.mdx b/api_docs/kbn_shared_ux_page_analytics_no_data.mdx index 9d3ee79325d6b6..4100d9f348c0f2 100644 --- a/api_docs/kbn_shared_ux_page_analytics_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_analytics_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-analytics-no-data title: "@kbn/shared-ux-page-analytics-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-analytics-no-data plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-analytics-no-data'] --- import kbnSharedUxPageAnalyticsNoDataObj from './kbn_shared_ux_page_analytics_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx b/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx index dc76c4c65277f6..bceeca64e06366 100644 --- a/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-analytics-no-data-mocks title: "@kbn/shared-ux-page-analytics-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-analytics-no-data-mocks plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-analytics-no-data-mocks'] --- import kbnSharedUxPageAnalyticsNoDataMocksObj from './kbn_shared_ux_page_analytics_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_no_data.mdx b/api_docs/kbn_shared_ux_page_kibana_no_data.mdx index f2ca862566e669..c492e180e625ec 100644 --- a/api_docs/kbn_shared_ux_page_kibana_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-no-data title: "@kbn/shared-ux-page-kibana-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-no-data plugin -date: 2023-11-06 +date: 2023-11-10 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 438b36a6209781..aebc621b8120b7 100644 --- a/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-no-data-mocks title: "@kbn/shared-ux-page-kibana-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-no-data-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 bda7cf25409f48..0afac69e5cc6aa 100644 --- a/api_docs/kbn_shared_ux_page_kibana_template.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_template.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-template title: "@kbn/shared-ux-page-kibana-template" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-template plugin -date: 2023-11-06 +date: 2023-11-10 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 391d703fa617d7..cdcdf4ec9c98c8 100644 --- a/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-template-mocks title: "@kbn/shared-ux-page-kibana-template-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-template-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 05be9e75661575..0b02f9e7ba2ee6 100644 --- a/api_docs/kbn_shared_ux_page_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data title: "@kbn/shared-ux-page-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data plugin -date: 2023-11-06 +date: 2023-11-10 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 50203e44441edb..052729a9704f61 100644 --- a/api_docs/kbn_shared_ux_page_no_data_config.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-config title: "@kbn/shared-ux-page-no-data-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-config plugin -date: 2023-11-06 +date: 2023-11-10 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 e02e81f626a24b..d534d9cbce8b6e 100644 --- a/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-config-mocks title: "@kbn/shared-ux-page-no-data-config-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-config-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 adfb36195e6189..2fc85cb04f0e4c 100644 --- a/api_docs/kbn_shared_ux_page_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-mocks title: "@kbn/shared-ux-page-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 434ed29e77d675..2865602f3f3cf7 100644 --- a/api_docs/kbn_shared_ux_page_solution_nav.mdx +++ b/api_docs/kbn_shared_ux_page_solution_nav.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-solution-nav title: "@kbn/shared-ux-page-solution-nav" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-solution-nav plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-solution-nav'] --- import kbnSharedUxPageSolutionNavObj from './kbn_shared_ux_page_solution_nav.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_prompt_no_data_views.mdx b/api_docs/kbn_shared_ux_prompt_no_data_views.mdx index b0fc7d3328fe60..f9b1d12110871b 100644 --- a/api_docs/kbn_shared_ux_prompt_no_data_views.mdx +++ b/api_docs/kbn_shared_ux_prompt_no_data_views.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-prompt-no-data-views title: "@kbn/shared-ux-prompt-no-data-views" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-prompt-no-data-views plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-prompt-no-data-views'] --- import kbnSharedUxPromptNoDataViewsObj from './kbn_shared_ux_prompt_no_data_views.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx b/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx index 9a413b32e32ec4..05246fe66de3af 100644 --- a/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx +++ b/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-prompt-no-data-views-mocks title: "@kbn/shared-ux-prompt-no-data-views-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-prompt-no-data-views-mocks plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-prompt-no-data-views-mocks'] --- import kbnSharedUxPromptNoDataViewsMocksObj from './kbn_shared_ux_prompt_no_data_views_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_prompt_not_found.mdx b/api_docs/kbn_shared_ux_prompt_not_found.mdx index dc7f4b22dc07e7..634779f091cb02 100644 --- a/api_docs/kbn_shared_ux_prompt_not_found.mdx +++ b/api_docs/kbn_shared_ux_prompt_not_found.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-prompt-not-found title: "@kbn/shared-ux-prompt-not-found" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-prompt-not-found plugin -date: 2023-11-06 +date: 2023-11-10 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 8d5bfc8093a026..15cac9a77a8a0f 100644 --- a/api_docs/kbn_shared_ux_router.mdx +++ b/api_docs/kbn_shared_ux_router.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-router title: "@kbn/shared-ux-router" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-router plugin -date: 2023-11-06 +date: 2023-11-10 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 0f8f12478e0287..68e84be049f44d 100644 --- a/api_docs/kbn_shared_ux_router_mocks.mdx +++ b/api_docs/kbn_shared_ux_router_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-router-mocks title: "@kbn/shared-ux-router-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-router-mocks plugin -date: 2023-11-06 +date: 2023-11-10 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 4af11951635268..48a8e9b21915a3 100644 --- a/api_docs/kbn_shared_ux_storybook_config.mdx +++ b/api_docs/kbn_shared_ux_storybook_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-storybook-config title: "@kbn/shared-ux-storybook-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-storybook-config plugin -date: 2023-11-06 +date: 2023-11-10 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 e27449178f964e..59e3d933077215 100644 --- a/api_docs/kbn_shared_ux_storybook_mock.mdx +++ b/api_docs/kbn_shared_ux_storybook_mock.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-storybook-mock title: "@kbn/shared-ux-storybook-mock" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-storybook-mock plugin -date: 2023-11-06 +date: 2023-11-10 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 ee573ce09e3750..cc9ffc9b40385b 100644 --- a/api_docs/kbn_shared_ux_utility.mdx +++ b/api_docs/kbn_shared_ux_utility.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-utility title: "@kbn/shared-ux-utility" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-utility plugin -date: 2023-11-06 +date: 2023-11-10 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 dd2e3dbef0a5a0..c782633b8fb9ba 100644 --- a/api_docs/kbn_slo_schema.devdocs.json +++ b/api_docs/kbn_slo_schema.devdocs.json @@ -482,7 +482,7 @@ "label": "CreateSLOInput", "description": [], "signature": [ - "{ name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; } & { id?: string | undefined; settings?: { syncDelay?: string | undefined; frequency?: string | undefined; } | undefined; tags?: string[] | undefined; groupBy?: string | undefined; }" + "{ name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; } & { id?: string | undefined; settings?: { syncDelay?: string | undefined; frequency?: string | undefined; } | undefined; tags?: string[] | undefined; groupBy?: string | undefined; }" ], "path": "x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts", "deprecated": false, @@ -497,7 +497,7 @@ "label": "CreateSLOParams", "description": [], "signature": [ - "{ name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: ", + "{ name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: ", { "pluginId": "@kbn/slo-schema", "scope": "common", @@ -631,7 +631,7 @@ "\nThe response type for /internal/observability/slo/_definitions\n" ], "signature": [ - "({ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { syncDelay: string; frequency: string; }; enabled: boolean; tags: string[]; groupBy: string; createdAt: string; updatedAt: string; } & { instanceId?: string | undefined; })[]" + "({ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { syncDelay: string; frequency: string; }; enabled: boolean; tags: string[]; groupBy: string; createdAt: string; updatedAt: string; } & { instanceId?: string | undefined; })[]" ], "path": "x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts", "deprecated": false, @@ -661,7 +661,7 @@ "label": "FindSLOResponse", "description": [], "signature": [ - "{ page: number; perPage: number; total: number; results: ({ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { syncDelay: string; frequency: string; }; enabled: boolean; tags: string[]; groupBy: string; createdAt: string; updatedAt: string; } & { instanceId?: string | undefined; } & { summary: { status: \"HEALTHY\" | \"NO_DATA\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; }; })[]; }" + "{ page: number; perPage: number; total: number; results: ({ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { syncDelay: string; frequency: string; }; enabled: boolean; tags: string[]; groupBy: string; createdAt: string; updatedAt: string; } & { instanceId?: string | undefined; } & { summary: { status: \"HEALTHY\" | \"NO_DATA\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; }; })[]; }" ], "path": "x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts", "deprecated": false, @@ -676,7 +676,7 @@ "label": "GetPreviewDataParams", "description": [], "signature": [ - "{ indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; }" + "{ indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; }" ], "path": "x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts", "deprecated": false, @@ -751,7 +751,7 @@ "label": "GetSLOResponse", "description": [], "signature": [ - "{ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { syncDelay: string; frequency: string; }; enabled: boolean; tags: string[]; groupBy: string; createdAt: string; updatedAt: string; } & { instanceId?: string | undefined; } & { summary: { status: \"HEALTHY\" | \"NO_DATA\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; }; }" + "{ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { syncDelay: string; frequency: string; }; enabled: boolean; tags: string[]; groupBy: string; createdAt: string; updatedAt: string; } & { instanceId?: string | undefined; } & { summary: { status: \"HEALTHY\" | \"NO_DATA\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; }; }" ], "path": "x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts", "deprecated": false, @@ -796,7 +796,7 @@ "label": "Indicator", "description": [], "signature": [ - "{ type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }" + "{ type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }" ], "path": "x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts", "deprecated": false, @@ -856,7 +856,7 @@ "label": "MetricCustomIndicator", "description": [], "signature": [ - "{ type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; }" + "{ type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; }" ], "path": "x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts", "deprecated": false, @@ -871,7 +871,7 @@ "label": "SLOResponse", "description": [], "signature": [ - "{ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { syncDelay: string; frequency: string; }; enabled: boolean; tags: string[]; groupBy: string; createdAt: string; updatedAt: string; } & { instanceId?: string | undefined; }" + "{ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { syncDelay: string; frequency: string; }; enabled: boolean; tags: string[]; groupBy: string; createdAt: string; updatedAt: string; } & { instanceId?: string | undefined; }" ], "path": "x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts", "deprecated": false, @@ -886,7 +886,7 @@ "label": "SLOWithSummaryResponse", "description": [], "signature": [ - "{ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { syncDelay: string; frequency: string; }; enabled: boolean; tags: string[]; groupBy: string; createdAt: string; updatedAt: string; } & { instanceId?: string | undefined; } & { summary: { status: \"HEALTHY\" | \"NO_DATA\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; }; }" + "{ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { syncDelay: string; frequency: string; }; enabled: boolean; tags: string[]; groupBy: string; createdAt: string; updatedAt: string; } & { instanceId?: string | undefined; } & { summary: { status: \"HEALTHY\" | \"NO_DATA\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; }; }" ], "path": "x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts", "deprecated": false, @@ -976,7 +976,7 @@ "label": "UpdateSLOInput", "description": [], "signature": [ - "{ name?: string | undefined; description?: string | undefined; indicator?: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; } | undefined; timeWindow?: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; } | undefined; budgetingMethod?: \"occurrences\" | \"timeslices\" | undefined; objective?: ({ target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }) | undefined; settings?: { syncDelay?: string | undefined; frequency?: string | undefined; } | undefined; tags?: string[] | undefined; groupBy?: string | undefined; }" + "{ name?: string | undefined; description?: string | undefined; indicator?: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; } | undefined; timeWindow?: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; } | undefined; budgetingMethod?: \"occurrences\" | \"timeslices\" | undefined; objective?: ({ target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }) | undefined; settings?: { syncDelay?: string | undefined; frequency?: string | undefined; } | undefined; tags?: string[] | undefined; groupBy?: string | undefined; }" ], "path": "x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts", "deprecated": false, @@ -991,7 +991,7 @@ "label": "UpdateSLOParams", "description": [], "signature": [ - "{ name?: string | undefined; description?: string | undefined; indicator?: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; } | undefined; timeWindow?: { duration: ", + "{ name?: string | undefined; description?: string | undefined; indicator?: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; } | undefined; timeWindow?: { duration: ", { "pluginId": "@kbn/slo-schema", "scope": "common", @@ -1046,7 +1046,7 @@ "label": "UpdateSLOResponse", "description": [], "signature": [ - "{ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { syncDelay: string; frequency: string; }; enabled: boolean; tags: string[]; groupBy: string; createdAt: string; updatedAt: string; } & { instanceId?: string | undefined; }" + "{ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { syncDelay: string; frequency: string; }; enabled: boolean; tags: string[]; groupBy: string; createdAt: string; updatedAt: string; } & { instanceId?: string | undefined; }" ], "path": "x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts", "deprecated": false, @@ -1409,40 +1409,68 @@ "<{ metrics: ", "ArrayC", "<", + "UnionC", + "<[", "IntersectionC", "<[", "TypeC", "<{ name: ", "StringC", "; aggregation: ", - "KeyofC", - "<{ sum: boolean; }>; field: ", + "LiteralC", + "<\"sum\">; field: ", "StringC", "; }>, ", "PartialC", "<{ filter: ", "StringC", - "; }>]>>; equation: ", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>>; equation: ", "StringC", "; }>; total: ", "TypeC", "<{ metrics: ", "ArrayC", "<", + "UnionC", + "<[", "IntersectionC", "<[", "TypeC", "<{ name: ", "StringC", "; aggregation: ", - "KeyofC", - "<{ sum: boolean; }>; field: ", + "LiteralC", + "<\"sum\">; field: ", "StringC", "; }>, ", "PartialC", "<{ filter: ", "StringC", - "; }>]>>; equation: ", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>>; equation: ", "StringC", "; }>; timestampField: ", "StringC", @@ -2075,40 +2103,68 @@ "<{ metrics: ", "ArrayC", "<", + "UnionC", + "<[", "IntersectionC", "<[", "TypeC", "<{ name: ", "StringC", "; aggregation: ", - "KeyofC", - "<{ sum: boolean; }>; field: ", + "LiteralC", + "<\"sum\">; field: ", "StringC", "; }>, ", "PartialC", "<{ filter: ", "StringC", - "; }>]>>; equation: ", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>>; equation: ", "StringC", "; }>; total: ", "TypeC", "<{ metrics: ", "ArrayC", "<", + "UnionC", + "<[", "IntersectionC", "<[", "TypeC", "<{ name: ", "StringC", "; aggregation: ", - "KeyofC", - "<{ sum: boolean; }>; field: ", + "LiteralC", + "<\"sum\">; field: ", "StringC", "; }>, ", "PartialC", "<{ filter: ", "StringC", - "; }>]>>; equation: ", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>>; equation: ", "StringC", "; }>; timestampField: ", "StringC", @@ -2557,40 +2613,68 @@ "<{ metrics: ", "ArrayC", "<", + "UnionC", + "<[", "IntersectionC", "<[", "TypeC", "<{ name: ", "StringC", "; aggregation: ", - "KeyofC", - "<{ sum: boolean; }>; field: ", + "LiteralC", + "<\"sum\">; field: ", "StringC", "; }>, ", "PartialC", "<{ filter: ", "StringC", - "; }>]>>; equation: ", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>>; equation: ", "StringC", "; }>; total: ", "TypeC", "<{ metrics: ", "ArrayC", "<", + "UnionC", + "<[", "IntersectionC", "<[", "TypeC", "<{ name: ", "StringC", "; aggregation: ", - "KeyofC", - "<{ sum: boolean; }>; field: ", + "LiteralC", + "<\"sum\">; field: ", "StringC", "; }>, ", "PartialC", "<{ filter: ", "StringC", - "; }>]>>; equation: ", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>>; equation: ", "StringC", "; }>; timestampField: ", "StringC", @@ -3007,40 +3091,68 @@ "<{ metrics: ", "ArrayC", "<", + "UnionC", + "<[", "IntersectionC", "<[", "TypeC", "<{ name: ", "StringC", "; aggregation: ", - "KeyofC", - "<{ sum: boolean; }>; field: ", + "LiteralC", + "<\"sum\">; field: ", "StringC", "; }>, ", "PartialC", "<{ filter: ", "StringC", - "; }>]>>; equation: ", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>>; equation: ", "StringC", "; }>; total: ", "TypeC", "<{ metrics: ", "ArrayC", "<", + "UnionC", + "<[", "IntersectionC", "<[", "TypeC", "<{ name: ", "StringC", "; aggregation: ", - "KeyofC", - "<{ sum: boolean; }>; field: ", + "LiteralC", + "<\"sum\">; field: ", "StringC", "; }>, ", "PartialC", "<{ filter: ", "StringC", - "; }>]>>; equation: ", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>>; equation: ", "StringC", "; }>; timestampField: ", "StringC", @@ -3501,40 +3613,68 @@ "<{ metrics: ", "ArrayC", "<", + "UnionC", + "<[", "IntersectionC", "<[", "TypeC", "<{ name: ", "StringC", "; aggregation: ", - "KeyofC", - "<{ sum: boolean; }>; field: ", + "LiteralC", + "<\"sum\">; field: ", "StringC", "; }>, ", "PartialC", "<{ filter: ", "StringC", - "; }>]>>; equation: ", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>>; equation: ", "StringC", "; }>; total: ", "TypeC", "<{ metrics: ", "ArrayC", "<", + "UnionC", + "<[", "IntersectionC", "<[", "TypeC", "<{ name: ", "StringC", "; aggregation: ", - "KeyofC", - "<{ sum: boolean; }>; field: ", + "LiteralC", + "<\"sum\">; field: ", "StringC", "; }>, ", "PartialC", "<{ filter: ", "StringC", - "; }>]>>; equation: ", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>>; equation: ", "StringC", "; }>; timestampField: ", "StringC", @@ -4123,40 +4263,68 @@ "<{ metrics: ", "ArrayC", "<", + "UnionC", + "<[", "IntersectionC", "<[", "TypeC", "<{ name: ", "StringC", "; aggregation: ", - "KeyofC", - "<{ sum: boolean; }>; field: ", + "LiteralC", + "<\"sum\">; field: ", "StringC", "; }>, ", "PartialC", "<{ filter: ", "StringC", - "; }>]>>; equation: ", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>>; equation: ", "StringC", "; }>; total: ", "TypeC", "<{ metrics: ", "ArrayC", "<", + "UnionC", + "<[", "IntersectionC", "<[", "TypeC", "<{ name: ", "StringC", "; aggregation: ", - "KeyofC", - "<{ sum: boolean; }>; field: ", + "LiteralC", + "<\"sum\">; field: ", "StringC", "; }>, ", "PartialC", "<{ filter: ", "StringC", - "; }>]>>; equation: ", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>>; equation: ", "StringC", "; }>; timestampField: ", "StringC", @@ -4431,6 +4599,60 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/slo-schema", + "id": "def-common.metricCustomBasicMetric", + "type": "Object", + "tags": [], + "label": "metricCustomBasicMetric", + "description": [], + "signature": [ + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"sum\">; field: ", + "StringC", + "; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>" + ], + "path": "x-pack/packages/kbn-slo-schema/src/schema/indicators.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/slo-schema", + "id": "def-common.metricCustomDocCountMetric", + "type": "Object", + "tags": [], + "label": "metricCustomDocCountMetric", + "description": [], + "signature": [ + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>" + ], + "path": "x-pack/packages/kbn-slo-schema/src/schema/indicators.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/slo-schema", "id": "def-common.metricCustomIndicatorSchema", @@ -4453,40 +4675,68 @@ "<{ metrics: ", "ArrayC", "<", + "UnionC", + "<[", "IntersectionC", "<[", "TypeC", "<{ name: ", "StringC", "; aggregation: ", - "KeyofC", - "<{ sum: boolean; }>; field: ", + "LiteralC", + "<\"sum\">; field: ", "StringC", "; }>, ", "PartialC", "<{ filter: ", "StringC", - "; }>]>>; equation: ", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>>; equation: ", "StringC", "; }>; total: ", "TypeC", "<{ metrics: ", "ArrayC", "<", + "UnionC", + "<[", "IntersectionC", "<[", "TypeC", "<{ name: ", "StringC", "; aggregation: ", - "KeyofC", - "<{ sum: boolean; }>; field: ", + "LiteralC", + "<\"sum\">; field: ", "StringC", "; }>, ", "PartialC", "<{ filter: ", "StringC", - "; }>]>>; equation: ", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>>; equation: ", "StringC", "; }>; timestampField: ", "StringC", @@ -4850,40 +5100,68 @@ "<{ metrics: ", "ArrayC", "<", + "UnionC", + "<[", "IntersectionC", "<[", "TypeC", "<{ name: ", "StringC", "; aggregation: ", - "KeyofC", - "<{ sum: boolean; }>; field: ", + "LiteralC", + "<\"sum\">; field: ", "StringC", "; }>, ", "PartialC", "<{ filter: ", "StringC", - "; }>]>>; equation: ", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>>; equation: ", "StringC", "; }>; total: ", "TypeC", "<{ metrics: ", "ArrayC", "<", + "UnionC", + "<[", "IntersectionC", "<[", "TypeC", "<{ name: ", "StringC", "; aggregation: ", - "KeyofC", - "<{ sum: boolean; }>; field: ", + "LiteralC", + "<\"sum\">; field: ", "StringC", "; }>, ", "PartialC", "<{ filter: ", "StringC", - "; }>]>>; equation: ", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>>; equation: ", "StringC", "; }>; timestampField: ", "StringC", @@ -5278,40 +5556,68 @@ "<{ metrics: ", "ArrayC", "<", + "UnionC", + "<[", "IntersectionC", "<[", "TypeC", "<{ name: ", "StringC", "; aggregation: ", - "KeyofC", - "<{ sum: boolean; }>; field: ", + "LiteralC", + "<\"sum\">; field: ", "StringC", "; }>, ", "PartialC", "<{ filter: ", "StringC", - "; }>]>>; equation: ", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>>; equation: ", "StringC", "; }>; total: ", "TypeC", "<{ metrics: ", "ArrayC", "<", + "UnionC", + "<[", "IntersectionC", "<[", "TypeC", "<{ name: ", "StringC", "; aggregation: ", - "KeyofC", - "<{ sum: boolean; }>; field: ", + "LiteralC", + "<\"sum\">; field: ", "StringC", "; }>, ", "PartialC", "<{ filter: ", "StringC", - "; }>]>>; equation: ", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>>; equation: ", "StringC", "; }>; timestampField: ", "StringC", @@ -5702,40 +6008,68 @@ "<{ metrics: ", "ArrayC", "<", + "UnionC", + "<[", "IntersectionC", "<[", "TypeC", "<{ name: ", "StringC", "; aggregation: ", - "KeyofC", - "<{ sum: boolean; }>; field: ", + "LiteralC", + "<\"sum\">; field: ", "StringC", "; }>, ", "PartialC", "<{ filter: ", "StringC", - "; }>]>>; equation: ", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>>; equation: ", "StringC", "; }>; total: ", "TypeC", "<{ metrics: ", "ArrayC", "<", + "UnionC", + "<[", "IntersectionC", "<[", "TypeC", "<{ name: ", "StringC", "; aggregation: ", - "KeyofC", - "<{ sum: boolean; }>; field: ", + "LiteralC", + "<\"sum\">; field: ", "StringC", "; }>, ", "PartialC", "<{ filter: ", "StringC", - "; }>]>>; equation: ", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>>; equation: ", "StringC", "; }>; timestampField: ", "StringC", @@ -6158,40 +6492,68 @@ "<{ metrics: ", "ArrayC", "<", + "UnionC", + "<[", "IntersectionC", "<[", "TypeC", "<{ name: ", "StringC", "; aggregation: ", - "KeyofC", - "<{ sum: boolean; }>; field: ", + "LiteralC", + "<\"sum\">; field: ", "StringC", "; }>, ", "PartialC", "<{ filter: ", "StringC", - "; }>]>>; equation: ", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>>; equation: ", "StringC", "; }>; total: ", "TypeC", "<{ metrics: ", "ArrayC", "<", + "UnionC", + "<[", "IntersectionC", "<[", "TypeC", "<{ name: ", "StringC", "; aggregation: ", - "KeyofC", - "<{ sum: boolean; }>; field: ", + "LiteralC", + "<\"sum\">; field: ", "StringC", "; }>, ", "PartialC", "<{ filter: ", "StringC", - "; }>]>>; equation: ", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>>; equation: ", "StringC", "; }>; timestampField: ", "StringC", @@ -7086,40 +7448,68 @@ "<{ metrics: ", "ArrayC", "<", + "UnionC", + "<[", "IntersectionC", "<[", "TypeC", "<{ name: ", "StringC", "; aggregation: ", - "KeyofC", - "<{ sum: boolean; }>; field: ", + "LiteralC", + "<\"sum\">; field: ", "StringC", "; }>, ", "PartialC", "<{ filter: ", "StringC", - "; }>]>>; equation: ", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>>; equation: ", "StringC", "; }>; total: ", "TypeC", "<{ metrics: ", "ArrayC", "<", + "UnionC", + "<[", "IntersectionC", "<[", "TypeC", "<{ name: ", "StringC", "; aggregation: ", - "KeyofC", - "<{ sum: boolean; }>; field: ", + "LiteralC", + "<\"sum\">; field: ", "StringC", "; }>, ", "PartialC", "<{ filter: ", "StringC", - "; }>]>>; equation: ", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>>; equation: ", "StringC", "; }>; timestampField: ", "StringC", @@ -7500,40 +7890,68 @@ "<{ metrics: ", "ArrayC", "<", + "UnionC", + "<[", "IntersectionC", "<[", "TypeC", "<{ name: ", "StringC", "; aggregation: ", - "KeyofC", - "<{ sum: boolean; }>; field: ", + "LiteralC", + "<\"sum\">; field: ", "StringC", "; }>, ", "PartialC", "<{ filter: ", "StringC", - "; }>]>>; equation: ", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>>; equation: ", "StringC", "; }>; total: ", "TypeC", "<{ metrics: ", "ArrayC", "<", + "UnionC", + "<[", "IntersectionC", "<[", "TypeC", "<{ name: ", "StringC", "; aggregation: ", - "KeyofC", - "<{ sum: boolean; }>; field: ", + "LiteralC", + "<\"sum\">; field: ", "StringC", "; }>, ", "PartialC", "<{ filter: ", "StringC", - "; }>]>>; equation: ", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>>; equation: ", "StringC", "; }>; timestampField: ", "StringC", diff --git a/api_docs/kbn_slo_schema.mdx b/api_docs/kbn_slo_schema.mdx index c34a25c6216430..de3cbf08d0c985 100644 --- a/api_docs/kbn_slo_schema.mdx +++ b/api_docs/kbn_slo_schema.mdx @@ -8,20 +8,20 @@ slug: /kibana-dev-docs/api/kbn-slo-schema title: "@kbn/slo-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/slo-schema plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/slo-schema'] --- import kbnSloSchemaObj from './kbn_slo_schema.devdocs.json'; -Contact [@elastic/actionable-observability](https://github.com/orgs/elastic/teams/actionable-observability) for questions regarding this plugin. +Contact [@elastic/obs-ux-management-team](https://github.com/orgs/elastic/teams/obs-ux-management-team) for questions regarding this plugin. **Code health stats** | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 129 | 0 | 126 | 0 | +| 131 | 0 | 128 | 0 | ## Common diff --git a/api_docs/kbn_some_dev_log.mdx b/api_docs/kbn_some_dev_log.mdx index b45f8cdf5c30d5..d847191cad35bd 100644 --- a/api_docs/kbn_some_dev_log.mdx +++ b/api_docs/kbn_some_dev_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-some-dev-log title: "@kbn/some-dev-log" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/some-dev-log plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/some-dev-log'] --- import kbnSomeDevLogObj from './kbn_some_dev_log.devdocs.json'; diff --git a/api_docs/kbn_std.mdx b/api_docs/kbn_std.mdx index 51f4e0bf57d2ca..6ccf9c12259832 100644 --- a/api_docs/kbn_std.mdx +++ b/api_docs/kbn_std.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-std title: "@kbn/std" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/std plugin -date: 2023-11-06 +date: 2023-11-10 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 febdde0070fba3..dad5ae36ef322a 100644 --- a/api_docs/kbn_stdio_dev_helpers.mdx +++ b/api_docs/kbn_stdio_dev_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-stdio-dev-helpers title: "@kbn/stdio-dev-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/stdio-dev-helpers plugin -date: 2023-11-06 +date: 2023-11-10 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 ee64f24bc5cd21..d26aa25b180680 100644 --- a/api_docs/kbn_storybook.mdx +++ b/api_docs/kbn_storybook.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-storybook title: "@kbn/storybook" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/storybook plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/storybook'] --- import kbnStorybookObj from './kbn_storybook.devdocs.json'; diff --git a/api_docs/kbn_subscription_tracking.mdx b/api_docs/kbn_subscription_tracking.mdx index 6cc8950b048718..aec338d9e3243f 100644 --- a/api_docs/kbn_subscription_tracking.mdx +++ b/api_docs/kbn_subscription_tracking.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-subscription-tracking title: "@kbn/subscription-tracking" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/subscription-tracking plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/subscription-tracking'] --- import kbnSubscriptionTrackingObj from './kbn_subscription_tracking.devdocs.json'; diff --git a/api_docs/kbn_telemetry_tools.mdx b/api_docs/kbn_telemetry_tools.mdx index 49861f073dbba5..f135c68fd82e81 100644 --- a/api_docs/kbn_telemetry_tools.mdx +++ b/api_docs/kbn_telemetry_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-telemetry-tools title: "@kbn/telemetry-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/telemetry-tools plugin -date: 2023-11-06 +date: 2023-11-10 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 11f6183166010a..f4c516a7b72c15 100644 --- a/api_docs/kbn_test.mdx +++ b/api_docs/kbn_test.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test title: "@kbn/test" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test'] --- import kbnTestObj from './kbn_test.devdocs.json'; diff --git a/api_docs/kbn_test_jest_helpers.mdx b/api_docs/kbn_test_jest_helpers.mdx index 20b762efcba30b..e8192760ab8c46 100644 --- a/api_docs/kbn_test_jest_helpers.mdx +++ b/api_docs/kbn_test_jest_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test-jest-helpers title: "@kbn/test-jest-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test-jest-helpers plugin -date: 2023-11-06 +date: 2023-11-10 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 83817b65ee3592..75a62bafb9c3c4 100644 --- a/api_docs/kbn_test_subj_selector.mdx +++ b/api_docs/kbn_test_subj_selector.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test-subj-selector title: "@kbn/test-subj-selector" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test-subj-selector plugin -date: 2023-11-06 +date: 2023-11-10 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.mdx b/api_docs/kbn_text_based_editor.mdx index 1da6c8a3563ffc..14dc22301d438f 100644 --- a/api_docs/kbn_text_based_editor.mdx +++ b/api_docs/kbn_text_based_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-text-based-editor title: "@kbn/text-based-editor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/text-based-editor plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/text-based-editor'] --- import kbnTextBasedEditorObj from './kbn_text_based_editor.devdocs.json'; diff --git a/api_docs/kbn_tooling_log.mdx b/api_docs/kbn_tooling_log.mdx index cd656fb6756c70..bce266d5e3d058 100644 --- a/api_docs/kbn_tooling_log.mdx +++ b/api_docs/kbn_tooling_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-tooling-log title: "@kbn/tooling-log" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/tooling-log plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/tooling-log'] --- import kbnToolingLogObj from './kbn_tooling_log.devdocs.json'; diff --git a/api_docs/kbn_ts_projects.mdx b/api_docs/kbn_ts_projects.mdx index 151329ba7e20d9..9adb1fd506d4c5 100644 --- a/api_docs/kbn_ts_projects.mdx +++ b/api_docs/kbn_ts_projects.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ts-projects title: "@kbn/ts-projects" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ts-projects plugin -date: 2023-11-06 +date: 2023-11-10 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 ab687f5fd0a4e8..920bdacfc58223 100644 --- a/api_docs/kbn_typed_react_router_config.mdx +++ b/api_docs/kbn_typed_react_router_config.mdx @@ -8,14 +8,14 @@ slug: /kibana-dev-docs/api/kbn-typed-react-router-config title: "@kbn/typed-react-router-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/typed-react-router-config plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/typed-react-router-config'] --- import kbnTypedReactRouterConfigObj from './kbn_typed_react_router_config.devdocs.json'; -Contact [@elastic/apm-ui](https://github.com/orgs/elastic/teams/apm-ui) for questions regarding this plugin. +Contact [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs-knowledge-team) for questions regarding this plugin. **Code health stats** diff --git a/api_docs/kbn_ui_actions_browser.mdx b/api_docs/kbn_ui_actions_browser.mdx index 5deea69179471f..ae156a3f67df1b 100644 --- a/api_docs/kbn_ui_actions_browser.mdx +++ b/api_docs/kbn_ui_actions_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ui-actions-browser title: "@kbn/ui-actions-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ui-actions-browser plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ui-actions-browser'] --- import kbnUiActionsBrowserObj from './kbn_ui_actions_browser.devdocs.json'; diff --git a/api_docs/kbn_ui_shared_deps_src.mdx b/api_docs/kbn_ui_shared_deps_src.mdx index bb90daa21c4e16..3aed0337f28758 100644 --- a/api_docs/kbn_ui_shared_deps_src.mdx +++ b/api_docs/kbn_ui_shared_deps_src.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ui-shared-deps-src title: "@kbn/ui-shared-deps-src" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ui-shared-deps-src plugin -date: 2023-11-06 +date: 2023-11-10 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 de0f9569fb7a51..33e8784499ade3 100644 --- a/api_docs/kbn_ui_theme.mdx +++ b/api_docs/kbn_ui_theme.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ui-theme title: "@kbn/ui-theme" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ui-theme plugin -date: 2023-11-06 +date: 2023-11-10 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 f2d9fd08a85eeb..02e6afdcfb395b 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: 2023-11-06 +date: 2023-11-10 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 4beef62fe278c4..f5e9c9d1f06bdf 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: 2023-11-06 +date: 2023-11-10 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 8e26f5eda8f034..ab60706a182c7c 100644 --- a/api_docs/kbn_unified_field_list.mdx +++ b/api_docs/kbn_unified_field_list.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-unified-field-list title: "@kbn/unified-field-list" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/unified-field-list plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/unified-field-list'] --- import kbnUnifiedFieldListObj from './kbn_unified_field_list.devdocs.json'; diff --git a/api_docs/kbn_url_state.mdx b/api_docs/kbn_url_state.mdx index d2172d54bf69b4..c8b7c990839ed2 100644 --- a/api_docs/kbn_url_state.mdx +++ b/api_docs/kbn_url_state.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-url-state title: "@kbn/url-state" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/url-state plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/url-state'] --- import kbnUrlStateObj from './kbn_url_state.devdocs.json'; diff --git a/api_docs/kbn_use_tracked_promise.mdx b/api_docs/kbn_use_tracked_promise.mdx index 33bd72647b2ae5..45233fbc1cedb1 100644 --- a/api_docs/kbn_use_tracked_promise.mdx +++ b/api_docs/kbn_use_tracked_promise.mdx @@ -8,14 +8,14 @@ slug: /kibana-dev-docs/api/kbn-use-tracked-promise title: "@kbn/use-tracked-promise" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/use-tracked-promise plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/use-tracked-promise'] --- import kbnUseTrackedPromiseObj from './kbn_use_tracked_promise.devdocs.json'; -Contact [@elastic/infra-monitoring-ui](https://github.com/orgs/elastic/teams/infra-monitoring-ui) for questions regarding this plugin. +Contact [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) for questions regarding this plugin. **Code health stats** diff --git a/api_docs/kbn_user_profile_components.mdx b/api_docs/kbn_user_profile_components.mdx index 5a0147a98a0bca..22ca484767fad0 100644 --- a/api_docs/kbn_user_profile_components.mdx +++ b/api_docs/kbn_user_profile_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-user-profile-components title: "@kbn/user-profile-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/user-profile-components plugin -date: 2023-11-06 +date: 2023-11-10 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 0b14f644b739d9..58719f00482f79 100644 --- a/api_docs/kbn_utility_types.mdx +++ b/api_docs/kbn_utility_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utility-types title: "@kbn/utility-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utility-types plugin -date: 2023-11-06 +date: 2023-11-10 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 3f755d6f5ebffa..6646d78632b752 100644 --- a/api_docs/kbn_utility_types_jest.mdx +++ b/api_docs/kbn_utility_types_jest.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utility-types-jest title: "@kbn/utility-types-jest" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utility-types-jest plugin -date: 2023-11-06 +date: 2023-11-10 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 a191365f339af2..f0758e421a49a2 100644 --- a/api_docs/kbn_utils.mdx +++ b/api_docs/kbn_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utils title: "@kbn/utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utils plugin -date: 2023-11-06 +date: 2023-11-10 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 dd0275bafe4cb3..2ea082493eb7bd 100644 --- a/api_docs/kbn_visualization_ui_components.mdx +++ b/api_docs/kbn_visualization_ui_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-visualization-ui-components title: "@kbn/visualization-ui-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/visualization-ui-components plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/visualization-ui-components'] --- import kbnVisualizationUiComponentsObj from './kbn_visualization_ui_components.devdocs.json'; diff --git a/api_docs/kbn_xstate_utils.mdx b/api_docs/kbn_xstate_utils.mdx index ed616752a137eb..b3047a5ff7a354 100644 --- a/api_docs/kbn_xstate_utils.mdx +++ b/api_docs/kbn_xstate_utils.mdx @@ -8,14 +8,14 @@ slug: /kibana-dev-docs/api/kbn-xstate-utils title: "@kbn/xstate-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/xstate-utils plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/xstate-utils'] --- import kbnXstateUtilsObj from './kbn_xstate_utils.devdocs.json'; -Contact [@elastic/infra-monitoring-ui](https://github.com/orgs/elastic/teams/infra-monitoring-ui) for questions regarding this plugin. +Contact [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) for questions regarding this plugin. **Code health stats** diff --git a/api_docs/kbn_yarn_lock_validator.mdx b/api_docs/kbn_yarn_lock_validator.mdx index 107d4aa3c92c7d..b5c7c8d40499e2 100644 --- a/api_docs/kbn_yarn_lock_validator.mdx +++ b/api_docs/kbn_yarn_lock_validator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-yarn-lock-validator title: "@kbn/yarn-lock-validator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/yarn-lock-validator plugin -date: 2023-11-06 +date: 2023-11-10 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.devdocs.json b/api_docs/kbn_zod_helpers.devdocs.json index aea5a227f726a3..2001282ced3945 100644 --- a/api_docs/kbn_zod_helpers.devdocs.json +++ b/api_docs/kbn_zod_helpers.devdocs.json @@ -19,6 +19,45 @@ "common": { "classes": [], "functions": [ + { + "parentPluginId": "@kbn/zod-helpers", + "id": "def-common.ArrayFromString", + "type": "Function", + "tags": [], + "label": "ArrayFromString", + "description": [ + "\nThis is a helper schema to convert comma separated strings to arrays. Useful\nfor processing query params.\n" + ], + "signature": [ + "(schema: T) => Zod.ZodEffects, T[\"_output\"][], unknown>" + ], + "path": "packages/kbn-zod-helpers/src/array_from_string.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/zod-helpers", + "id": "def-common.ArrayFromString.$1", + "type": "Uncategorized", + "tags": [], + "label": "schema", + "description": [ + "Array items schema" + ], + "signature": [ + "T" + ], + "path": "packages/kbn-zod-helpers/src/array_from_string.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [ + "Array schema that accepts a comma-separated string as input" + ], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/zod-helpers", "id": "def-common.expectParseError", @@ -180,6 +219,62 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/zod-helpers", + "id": "def-common.safeParseResult", + "type": "Function", + "tags": [], + "label": "safeParseResult", + "description": [ + "\nSafely parse a payload against a schema, returning the output or undefined.\nThis method does not throw validation errors and is useful for validating\noptional objects when we don't care about errors.\n" + ], + "signature": [ + "(payload: unknown, schema: T) => T[\"_output\"] | undefined" + ], + "path": "packages/kbn-zod-helpers/src/safe_parse_result.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/zod-helpers", + "id": "def-common.safeParseResult.$1", + "type": "Unknown", + "tags": [], + "label": "payload", + "description": [ + "Schema payload" + ], + "signature": [ + "unknown" + ], + "path": "packages/kbn-zod-helpers/src/safe_parse_result.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/zod-helpers", + "id": "def-common.safeParseResult.$2", + "type": "Uncategorized", + "tags": [], + "label": "schema", + "description": [ + "Validation schema" + ], + "signature": [ + "T" + ], + "path": "packages/kbn-zod-helpers/src/safe_parse_result.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [ + "Schema output or undefined" + ], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/zod-helpers", "id": "def-common.stringifyZodError", @@ -237,6 +332,24 @@ "initialIsOpen": false } ], - "objects": [] + "objects": [ + { + "parentPluginId": "@kbn/zod-helpers", + "id": "def-common.BooleanFromString", + "type": "Object", + "tags": [], + "label": "BooleanFromString", + "description": [ + "\nThis is a helper schema to convert a boolean string (\"true\" or \"false\") to a\nboolean. Useful for processing query params.\n\nAccepts \"true\" or \"false\" as strings, or a boolean." + ], + "signature": [ + "Zod.ZodEffects, Zod.ZodBoolean]>, boolean, boolean | \"true\" | \"false\">" + ], + "path": "packages/kbn-zod-helpers/src/boolean_from_string.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ] } } \ No newline at end of file diff --git a/api_docs/kbn_zod_helpers.mdx b/api_docs/kbn_zod_helpers.mdx index 49ece4f6d0b631..3f2411d31c72f4 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: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/zod-helpers'] --- import kbnZodHelpersObj from './kbn_zod_helpers.devdocs.json'; @@ -21,10 +21,13 @@ Contact [@elastic/security-detection-rule-management](https://github.com/orgs/el | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 12 | 0 | 9 | 0 | +| 18 | 0 | 9 | 0 | ## Common +### Objects + + ### Functions diff --git a/api_docs/kibana_overview.mdx b/api_docs/kibana_overview.mdx index 21d7a921834d25..2cee78c2e41e9f 100644 --- a/api_docs/kibana_overview.mdx +++ b/api_docs/kibana_overview.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaOverview title: "kibanaOverview" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaOverview plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaOverview'] --- import kibanaOverviewObj from './kibana_overview.devdocs.json'; diff --git a/api_docs/kibana_react.mdx b/api_docs/kibana_react.mdx index 16095fc6441ebb..22882b08c02f8e 100644 --- a/api_docs/kibana_react.mdx +++ b/api_docs/kibana_react.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaReact title: "kibanaReact" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaReact plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaReact'] --- import kibanaReactObj from './kibana_react.devdocs.json'; diff --git a/api_docs/kibana_utils.mdx b/api_docs/kibana_utils.mdx index e9b056b9445b74..505cd1eb8ac16b 100644 --- a/api_docs/kibana_utils.mdx +++ b/api_docs/kibana_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaUtils title: "kibanaUtils" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaUtils plugin -date: 2023-11-06 +date: 2023-11-10 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 b4b7617296c84f..810752c805ff30 100644 --- a/api_docs/kubernetes_security.mdx +++ b/api_docs/kubernetes_security.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kubernetesSecurity title: "kubernetesSecurity" image: https://source.unsplash.com/400x175/?github description: API docs for the kubernetesSecurity plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kubernetesSecurity'] --- import kubernetesSecurityObj from './kubernetes_security.devdocs.json'; diff --git a/api_docs/lens.mdx b/api_docs/lens.mdx index 145ea960d21b8b..c2f0248c6aa2f6 100644 --- a/api_docs/lens.mdx +++ b/api_docs/lens.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/lens title: "lens" image: https://source.unsplash.com/400x175/?github description: API docs for the lens plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lens'] --- import lensObj from './lens.devdocs.json'; diff --git a/api_docs/license_api_guard.mdx b/api_docs/license_api_guard.mdx index 7686b321942407..b1ccd2542831ff 100644 --- a/api_docs/license_api_guard.mdx +++ b/api_docs/license_api_guard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licenseApiGuard title: "licenseApiGuard" image: https://source.unsplash.com/400x175/?github description: API docs for the licenseApiGuard plugin -date: 2023-11-06 +date: 2023-11-10 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 a362a86823ac54..4c76a1a3e2ee4c 100644 --- a/api_docs/license_management.mdx +++ b/api_docs/license_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licenseManagement title: "licenseManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the licenseManagement plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licenseManagement'] --- import licenseManagementObj from './license_management.devdocs.json'; diff --git a/api_docs/licensing.mdx b/api_docs/licensing.mdx index 93fd4daead6e9c..7521a9dc4ac281 100644 --- a/api_docs/licensing.mdx +++ b/api_docs/licensing.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licensing title: "licensing" image: https://source.unsplash.com/400x175/?github description: API docs for the licensing plugin -date: 2023-11-06 +date: 2023-11-10 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 fe1d497af89237..ac1a8616271092 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: 2023-11-06 +date: 2023-11-10 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 84622c0f7a0e4d..c19ec2587da2e9 100644 --- a/api_docs/lists.mdx +++ b/api_docs/lists.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/lists title: "lists" image: https://source.unsplash.com/400x175/?github description: API docs for the lists plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lists'] --- import listsObj from './lists.devdocs.json'; diff --git a/api_docs/log_explorer.devdocs.json b/api_docs/log_explorer.devdocs.json index 238163290a823c..783b558b4505e6 100644 --- a/api_docs/log_explorer.devdocs.json +++ b/api_docs/log_explorer.devdocs.json @@ -4,6 +4,64 @@ "classes": [], "functions": [], "interfaces": [ + { + "parentPluginId": "logExplorer", + "id": "def-public.LogExplorerCustomizations", + "type": "Interface", + "tags": [], + "label": "LogExplorerCustomizations", + "description": [], + "path": "x-pack/plugins/log_explorer/public/components/log_explorer/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "logExplorer", + "id": "def-public.LogExplorerCustomizations.flyout", + "type": "Object", + "tags": [], + "label": "flyout", + "description": [], + "signature": [ + "{ renderContent?: ", + "FlyoutRenderContent", + " | undefined; } | undefined" + ], + "path": "x-pack/plugins/log_explorer/public/components/log_explorer/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "logExplorer", + "id": "def-public.LogExplorerFlyoutContentProps", + "type": "Interface", + "tags": [], + "label": "LogExplorerFlyoutContentProps", + "description": [], + "path": "x-pack/plugins/log_explorer/public/components/log_explorer/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "logExplorer", + "id": "def-public.LogExplorerFlyoutContentProps.doc", + "type": "Object", + "tags": [], + "label": "doc", + "description": [], + "signature": [ + "DataTableRecord" + ], + "path": "x-pack/plugins/log_explorer/public/components/log_explorer/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "logExplorer", "id": "def-public.LogExplorerStateContainer", diff --git a/api_docs/log_explorer.mdx b/api_docs/log_explorer.mdx index c74889ed2c6e15..f8dc452b80c8fb 100644 --- a/api_docs/log_explorer.mdx +++ b/api_docs/log_explorer.mdx @@ -8,20 +8,20 @@ slug: /kibana-dev-docs/api/logExplorer title: "logExplorer" image: https://source.unsplash.com/400x175/?github description: API docs for the logExplorer plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'logExplorer'] --- import logExplorerObj from './log_explorer.devdocs.json'; This plugin provides a LogExplorer component using the Discover customization framework, offering several affordances specifically designed for log consumption. -Contact [@elastic/infra-monitoring-ui](https://github.com/orgs/elastic/teams/infra-monitoring-ui) for questions regarding this plugin. +Contact [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) for questions regarding this plugin. **Code health stats** | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 22 | 0 | 22 | 7 | +| 26 | 0 | 26 | 8 | ## Client diff --git a/api_docs/logs_shared.devdocs.json b/api_docs/logs_shared.devdocs.json index e911fdaa0f16b9..7bb6418a73bc78 100644 --- a/api_docs/logs_shared.devdocs.json +++ b/api_docs/logs_shared.devdocs.json @@ -262,6 +262,38 @@ "returnComment": [], "initialIsOpen": false }, + { + "parentPluginId": "logsShared", + "id": "def-public.LogAIAssistant", + "type": "Function", + "tags": [], + "label": "LogAIAssistant", + "description": [], + "signature": [ + "(props: any) => JSX.Element" + ], + "path": "x-pack/plugins/logs_shared/public/index.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "logsShared", + "id": "def-public.LogAIAssistant.$1", + "type": "Any", + "tags": [], + "label": "props", + "description": [], + "signature": [ + "any" + ], + "path": "x-pack/plugins/logs_shared/common/dynamic.tsx", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "logsShared", "id": "def-public.LogColumnHeader", @@ -2546,6 +2578,77 @@ } ], "interfaces": [ + { + "parentPluginId": "logsShared", + "id": "def-public.LogAIAssistantDocument", + "type": "Interface", + "tags": [], + "label": "LogAIAssistantDocument", + "description": [], + "path": "x-pack/plugins/logs_shared/public/components/log_ai_assistant/log_ai_assistant.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "logsShared", + "id": "def-public.LogAIAssistantDocument.fields", + "type": "Array", + "tags": [], + "label": "fields", + "description": [], + "signature": [ + "{ field: string; value: ", + { + "pluginId": "@kbn/utility-types", + "scope": "common", + "docId": "kibKbnUtilityTypesPluginApi", + "section": "def-common.JsonArray", + "text": "JsonArray" + }, + "; }[]" + ], + "path": "x-pack/plugins/logs_shared/public/components/log_ai_assistant/log_ai_assistant.tsx", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "logsShared", + "id": "def-public.LogAIAssistantProps", + "type": "Interface", + "tags": [], + "label": "LogAIAssistantProps", + "description": [], + "path": "x-pack/plugins/logs_shared/public/components/log_ai_assistant/log_ai_assistant.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "logsShared", + "id": "def-public.LogAIAssistantProps.doc", + "type": "Object", + "tags": [], + "label": "doc", + "description": [], + "signature": [ + { + "pluginId": "logsShared", + "scope": "public", + "docId": "kibLogsSharedPluginApi", + "section": "def-public.LogAIAssistantDocument", + "text": "LogAIAssistantDocument" + }, + " | undefined" + ], + "path": "x-pack/plugins/logs_shared/public/components/log_ai_assistant/log_ai_assistant.tsx", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "logsShared", "id": "def-public.LogEntryColumnWidths", @@ -2741,6 +2844,26 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "logsShared", + "id": "def-public.LogsSharedClientStartDeps.observabilityAIAssistant", + "type": "Object", + "tags": [], + "label": "observabilityAIAssistant", + "description": [], + "signature": [ + { + "pluginId": "observabilityAIAssistant", + "scope": "public", + "docId": "kibObservabilityAIAssistantPluginApi", + "section": "def-public.ObservabilityAIAssistantPluginStart", + "text": "ObservabilityAIAssistantPluginStart" + } + ], + "path": "x-pack/plugins/logs_shared/public/types.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "logsShared", "id": "def-public.LogsSharedClientStartDeps.uiActions", @@ -3182,6 +3305,28 @@ "path": "x-pack/plugins/logs_shared/public/types.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "logsShared", + "id": "def-public.LogsSharedClientStartExports.LogAIAssistant", + "type": "CompoundType", + "tags": [], + "label": "LogAIAssistant", + "description": [], + "signature": [ + "React.ComponentClass<", + "Optional", + "<", + "LogAIAssistantDeps", + ", \"observabilityAIAssistant\">, any> | React.FunctionComponent<", + "Optional", + "<", + "LogAIAssistantDeps", + ", \"observabilityAIAssistant\">>" + ], + "path": "x-pack/plugins/logs_shared/public/types.ts", + "deprecated": false, + "trackAdoption": false } ], "lifecycle": "start", diff --git a/api_docs/logs_shared.mdx b/api_docs/logs_shared.mdx index faa737d3021d1d..1bcc3fe34feec6 100644 --- a/api_docs/logs_shared.mdx +++ b/api_docs/logs_shared.mdx @@ -8,20 +8,20 @@ slug: /kibana-dev-docs/api/logsShared title: "logsShared" image: https://source.unsplash.com/400x175/?github description: API docs for the logsShared plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'logsShared'] --- import logsSharedObj from './logs_shared.devdocs.json'; Exposes the shared components and APIs to access and visualize logs. -Contact [@elastic/infra-monitoring-ui](https://github.com/orgs/elastic/teams/infra-monitoring-ui) for questions regarding this plugin. +Contact [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) for questions regarding this plugin. **Code health stats** | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 279 | 10 | 264 | 27 | +| 287 | 11 | 272 | 28 | ## Client diff --git a/api_docs/management.mdx b/api_docs/management.mdx index aa91fdfa8993c1..aa783f0036d36f 100644 --- a/api_docs/management.mdx +++ b/api_docs/management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/management title: "management" image: https://source.unsplash.com/400x175/?github description: API docs for the management plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'management'] --- import managementObj from './management.devdocs.json'; diff --git a/api_docs/maps.devdocs.json b/api_docs/maps.devdocs.json index 2912fb874ff47f..b95d0314243bec 100644 --- a/api_docs/maps.devdocs.json +++ b/api_docs/maps.devdocs.json @@ -157,6 +157,22 @@ "trackAdoption": false, "children": [], "returnComment": [] + }, + { + "parentPluginId": "maps", + "id": "def-public.DataRequest.getError", + "type": "Function", + "tags": [], + "label": "getError", + "description": [], + "signature": [ + "() => string | undefined" + ], + "path": "x-pack/plugins/maps/public/classes/util/data_request.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] } ], "initialIsOpen": false @@ -4604,7 +4620,7 @@ "signature": [ "{ __dataRequests?: ", "DataRequestDescriptor", - "[] | undefined; __isInErrorState?: boolean | undefined; __isPreviewLayer?: boolean | undefined; __errorMessage?: string | undefined; __trackedLayerDescriptor?: ", + "[] | undefined; __isPreviewLayer?: boolean | undefined; __trackedLayerDescriptor?: ", { "pluginId": "maps", "scope": "common", @@ -4612,8 +4628,10 @@ "section": "def-common.LayerDescriptor", "text": "LayerDescriptor" }, - " | undefined; __areTilesLoaded?: boolean | undefined; __metaFromTiles?: ", + " | undefined; __areTilesLoaded?: boolean | undefined; __tileMetaFeatures?: ", "TileMetaFeature", + "[] | undefined; __tileErrors?: ", + "TileError", "[] | undefined; alpha?: number | undefined; attribution?: ", "Attribution", " | undefined; id: string; label?: string | null | undefined; locale?: string | null | undefined; areLabelsOnTop?: boolean | undefined; minZoom?: number | undefined; maxZoom?: number | undefined; sourceDescriptor: ", diff --git a/api_docs/maps.mdx b/api_docs/maps.mdx index a5feddb467c07f..f5d502d0bf5a50 100644 --- a/api_docs/maps.mdx +++ b/api_docs/maps.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/maps title: "maps" image: https://source.unsplash.com/400x175/?github description: API docs for the maps plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'maps'] --- import mapsObj from './maps.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-gis](https://github.com/orgs/elastic/teams/kibana-gis) | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 259 | 0 | 258 | 28 | +| 260 | 0 | 259 | 29 | ## Client diff --git a/api_docs/maps_ems.mdx b/api_docs/maps_ems.mdx index 5049b83665709d..f007519399b52d 100644 --- a/api_docs/maps_ems.mdx +++ b/api_docs/maps_ems.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/mapsEms title: "mapsEms" image: https://source.unsplash.com/400x175/?github description: API docs for the mapsEms plugin -date: 2023-11-06 +date: 2023-11-10 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 5043c3dde5fb21..7852488e4c3944 100644 --- a/api_docs/metrics_data_access.mdx +++ b/api_docs/metrics_data_access.mdx @@ -8,14 +8,14 @@ slug: /kibana-dev-docs/api/metricsDataAccess title: "metricsDataAccess" image: https://source.unsplash.com/400x175/?github description: API docs for the metricsDataAccess plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'metricsDataAccess'] --- import metricsDataAccessObj from './metrics_data_access.devdocs.json'; Exposes utilities for accessing metrics data -Contact [@elastic/infra-monitoring-ui](https://github.com/orgs/elastic/teams/infra-monitoring-ui) for questions regarding this plugin. +Contact [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs-knowledge-team) for questions regarding this plugin. **Code health stats** diff --git a/api_docs/ml.mdx b/api_docs/ml.mdx index 308adfc7bc0c42..4777f154c8204d 100644 --- a/api_docs/ml.mdx +++ b/api_docs/ml.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ml title: "ml" image: https://source.unsplash.com/400x175/?github description: API docs for the ml plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ml'] --- import mlObj from './ml.devdocs.json'; diff --git a/api_docs/monitoring.mdx b/api_docs/monitoring.mdx index 498fd70c0c9f7e..74f95298cc6d23 100644 --- a/api_docs/monitoring.mdx +++ b/api_docs/monitoring.mdx @@ -8,14 +8,14 @@ slug: /kibana-dev-docs/api/monitoring title: "monitoring" image: https://source.unsplash.com/400x175/?github description: API docs for the monitoring plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'monitoring'] --- import monitoringObj from './monitoring.devdocs.json'; -Contact [@elastic/infra-monitoring-ui](https://github.com/orgs/elastic/teams/infra-monitoring-ui) for questions regarding this plugin. +Contact [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) for questions regarding this plugin. **Code health stats** diff --git a/api_docs/monitoring_collection.mdx b/api_docs/monitoring_collection.mdx index db66874d050bd6..d96d48b661d551 100644 --- a/api_docs/monitoring_collection.mdx +++ b/api_docs/monitoring_collection.mdx @@ -8,14 +8,14 @@ slug: /kibana-dev-docs/api/monitoringCollection title: "monitoringCollection" image: https://source.unsplash.com/400x175/?github description: API docs for the monitoringCollection plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'monitoringCollection'] --- import monitoringCollectionObj from './monitoring_collection.devdocs.json'; -Contact [@elastic/infra-monitoring-ui](https://github.com/orgs/elastic/teams/infra-monitoring-ui) for questions regarding this plugin. +Contact [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) for questions regarding this plugin. **Code health stats** diff --git a/api_docs/navigation.mdx b/api_docs/navigation.mdx index 6c72b98044cf34..238e7abab807a2 100644 --- a/api_docs/navigation.mdx +++ b/api_docs/navigation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/navigation title: "navigation" image: https://source.unsplash.com/400x175/?github description: API docs for the navigation plugin -date: 2023-11-06 +date: 2023-11-10 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 d410e23321cbde..c09b5ff9ce8ba0 100644 --- a/api_docs/newsfeed.mdx +++ b/api_docs/newsfeed.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/newsfeed title: "newsfeed" image: https://source.unsplash.com/400x175/?github description: API docs for the newsfeed plugin -date: 2023-11-06 +date: 2023-11-10 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 e6af864399727f..d324fbaf4638fb 100644 --- a/api_docs/no_data_page.mdx +++ b/api_docs/no_data_page.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/noDataPage title: "noDataPage" image: https://source.unsplash.com/400x175/?github description: API docs for the noDataPage plugin -date: 2023-11-06 +date: 2023-11-10 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 b19753524ebd21..9be02bb30098e5 100644 --- a/api_docs/notifications.mdx +++ b/api_docs/notifications.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/notifications title: "notifications" image: https://source.unsplash.com/400x175/?github description: API docs for the notifications plugin -date: 2023-11-06 +date: 2023-11-10 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 06942864aef609..0610ed9ace5c93 100644 --- a/api_docs/observability.devdocs.json +++ b/api_docs/observability.devdocs.json @@ -8297,40 +8297,68 @@ "<{ metrics: ", "ArrayC", "<", + "UnionC", + "<[", "IntersectionC", "<[", "TypeC", "<{ name: ", "StringC", "; aggregation: ", - "KeyofC", - "<{ sum: boolean; }>; field: ", + "LiteralC", + "<\"sum\">; field: ", "StringC", "; }>, ", "PartialC", "<{ filter: ", "StringC", - "; }>]>>; equation: ", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>>; equation: ", "StringC", "; }>; total: ", "TypeC", "<{ metrics: ", "ArrayC", "<", + "UnionC", + "<[", "IntersectionC", "<[", "TypeC", "<{ name: ", "StringC", "; aggregation: ", - "KeyofC", - "<{ sum: boolean; }>; field: ", + "LiteralC", + "<\"sum\">; field: ", "StringC", "; }>, ", "PartialC", "<{ filter: ", "StringC", - "; }>]>>; equation: ", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>>; equation: ", "StringC", "; }>; timestampField: ", "StringC", @@ -8492,7 +8520,7 @@ "section": "def-server.ObservabilityRouteHandlerResources", "text": "ObservabilityRouteHandlerResources" }, - " & { params: { body: { indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; }; }; }) => Promise<{ date: string; sliValue: number; }[]>; } & ", + " & { params: { body: { indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; }; }; }) => Promise<{ date: string; sliValue: number; }[]>; } & ", { "pluginId": "observability", "scope": "server", @@ -8703,40 +8731,68 @@ "<{ metrics: ", "ArrayC", "<", + "UnionC", + "<[", "IntersectionC", "<[", "TypeC", "<{ name: ", "StringC", "; aggregation: ", - "KeyofC", - "<{ sum: boolean; }>; field: ", + "LiteralC", + "<\"sum\">; field: ", "StringC", "; }>, ", "PartialC", "<{ filter: ", "StringC", - "; }>]>>; equation: ", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>>; equation: ", "StringC", "; }>; total: ", "TypeC", "<{ metrics: ", "ArrayC", "<", + "UnionC", + "<[", "IntersectionC", "<[", "TypeC", "<{ name: ", "StringC", "; aggregation: ", - "KeyofC", - "<{ sum: boolean; }>; field: ", + "LiteralC", + "<\"sum\">; field: ", "StringC", "; }>, ", "PartialC", "<{ filter: ", "StringC", - "; }>]>>; equation: ", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>>; equation: ", "StringC", "; }>; timestampField: ", "StringC", @@ -8986,7 +9042,7 @@ "section": "def-server.ObservabilityRouteHandlerResources", "text": "ObservabilityRouteHandlerResources" }, - " & { params: { path: { id: string; }; body: { name?: string | undefined; description?: string | undefined; indicator?: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; } | undefined; timeWindow?: { duration: ", + " & { params: { path: { id: string; }; body: { name?: string | undefined; description?: string | undefined; indicator?: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; } | undefined; timeWindow?: { duration: ", { "pluginId": "@kbn/slo-schema", "scope": "common", @@ -9026,7 +9082,7 @@ "section": "def-common.Duration", "text": "Duration" }, - " | undefined; } | undefined; tags?: string[] | undefined; groupBy?: string | undefined; }; }; }) => Promise<{ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { syncDelay: string; frequency: string; }; enabled: boolean; tags: string[]; groupBy: string; createdAt: string; updatedAt: string; } & { instanceId?: string | undefined; }>; } & ", + " | undefined; } | undefined; tags?: string[] | undefined; groupBy?: string | undefined; }; }; }) => Promise<{ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { syncDelay: string; frequency: string; }; enabled: boolean; tags: string[]; groupBy: string; createdAt: string; updatedAt: string; } & { instanceId?: string | undefined; }>; } & ", { "pluginId": "observability", "scope": "server", @@ -9060,7 +9116,7 @@ "section": "def-server.ObservabilityRouteHandlerResources", "text": "ObservabilityRouteHandlerResources" }, - " & { params: { path: { id: string; }; } & { query?: { instanceId?: string | undefined; } | undefined; }; }) => Promise<{ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { syncDelay: string; frequency: string; }; enabled: boolean; tags: string[]; groupBy: string; createdAt: string; updatedAt: string; } & { instanceId?: string | undefined; } & { summary: { status: \"HEALTHY\" | \"NO_DATA\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; }; }>; } & ", + " & { params: { path: { id: string; }; } & { query?: { instanceId?: string | undefined; } | undefined; }; }) => Promise<{ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { syncDelay: string; frequency: string; }; enabled: boolean; tags: string[]; groupBy: string; createdAt: string; updatedAt: string; } & { instanceId?: string | undefined; } & { summary: { status: \"HEALTHY\" | \"NO_DATA\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; }; }>; } & ", { "pluginId": "observability", "scope": "server", @@ -9102,7 +9158,7 @@ "section": "def-server.ObservabilityRouteHandlerResources", "text": "ObservabilityRouteHandlerResources" }, - " & { params?: { query?: { kqlQuery?: string | undefined; page?: string | undefined; perPage?: string | undefined; sortBy?: \"status\" | \"error_budget_consumed\" | \"error_budget_remaining\" | \"sli_value\" | undefined; sortDirection?: \"asc\" | \"desc\" | undefined; } | undefined; } | undefined; }) => Promise<{ page: number; perPage: number; total: number; results: ({ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { syncDelay: string; frequency: string; }; enabled: boolean; tags: string[]; groupBy: string; createdAt: string; updatedAt: string; } & { instanceId?: string | undefined; } & { summary: { status: \"HEALTHY\" | \"NO_DATA\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; }; })[]; }>; } & ", + " & { params?: { query?: { kqlQuery?: string | undefined; page?: string | undefined; perPage?: string | undefined; sortBy?: \"status\" | \"error_budget_consumed\" | \"error_budget_remaining\" | \"sli_value\" | undefined; sortDirection?: \"asc\" | \"desc\" | undefined; } | undefined; } | undefined; }) => Promise<{ page: number; perPage: number; total: number; results: ({ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { syncDelay: string; frequency: string; }; enabled: boolean; tags: string[]; groupBy: string; createdAt: string; updatedAt: string; } & { instanceId?: string | undefined; } & { summary: { status: \"HEALTHY\" | \"NO_DATA\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; }; })[]; }>; } & ", { "pluginId": "observability", "scope": "server", @@ -9124,7 +9180,7 @@ "section": "def-server.ObservabilityRouteHandlerResources", "text": "ObservabilityRouteHandlerResources" }, - " & { params: { query: { search: string; }; }; }) => Promise<({ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { syncDelay: string; frequency: string; }; enabled: boolean; tags: string[]; groupBy: string; createdAt: string; updatedAt: string; } & { instanceId?: string | undefined; })[]>; } & ", + " & { params: { query: { search: string; }; }; }) => Promise<({ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { syncDelay: string; frequency: string; }; enabled: boolean; tags: string[]; groupBy: string; createdAt: string; updatedAt: string; } & { instanceId?: string | undefined; })[]>; } & ", { "pluginId": "observability", "scope": "server", @@ -9383,40 +9439,68 @@ "<{ metrics: ", "ArrayC", "<", + "UnionC", + "<[", "IntersectionC", "<[", "TypeC", "<{ name: ", "StringC", "; aggregation: ", - "KeyofC", - "<{ sum: boolean; }>; field: ", + "LiteralC", + "<\"sum\">; field: ", "StringC", "; }>, ", "PartialC", "<{ filter: ", "StringC", - "; }>]>>; equation: ", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>>; equation: ", "StringC", "; }>; total: ", "TypeC", "<{ metrics: ", "ArrayC", "<", + "UnionC", + "<[", "IntersectionC", "<[", "TypeC", "<{ name: ", "StringC", "; aggregation: ", - "KeyofC", - "<{ sum: boolean; }>; field: ", + "LiteralC", + "<\"sum\">; field: ", "StringC", "; }>, ", "PartialC", "<{ filter: ", "StringC", - "; }>]>>; equation: ", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>>; equation: ", "StringC", "; }>; timestampField: ", "StringC", @@ -9670,7 +9754,7 @@ "section": "def-server.ObservabilityRouteHandlerResources", "text": "ObservabilityRouteHandlerResources" }, - " & { params: { body: { name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: ", + " & { params: { body: { name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: ", { "pluginId": "@kbn/slo-schema", "scope": "common", @@ -9917,40 +10001,68 @@ "<{ metrics: ", "ArrayC", "<", + "UnionC", + "<[", "IntersectionC", "<[", "TypeC", "<{ name: ", "StringC", "; aggregation: ", - "KeyofC", - "<{ sum: boolean; }>; field: ", + "LiteralC", + "<\"sum\">; field: ", "StringC", "; }>, ", "PartialC", "<{ filter: ", "StringC", - "; }>]>>; equation: ", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>>; equation: ", "StringC", "; }>; total: ", "TypeC", "<{ metrics: ", "ArrayC", "<", + "UnionC", + "<[", "IntersectionC", "<[", "TypeC", "<{ name: ", "StringC", "; aggregation: ", - "KeyofC", - "<{ sum: boolean; }>; field: ", + "LiteralC", + "<\"sum\">; field: ", "StringC", "; }>, ", "PartialC", "<{ filter: ", "StringC", - "; }>]>>; equation: ", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>>; equation: ", "StringC", "; }>; timestampField: ", "StringC", @@ -10112,7 +10224,7 @@ "section": "def-server.ObservabilityRouteHandlerResources", "text": "ObservabilityRouteHandlerResources" }, - " & { params: { body: { indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; }; }; }) => Promise<{ date: string; sliValue: number; }[]>; } & ", + " & { params: { body: { indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; }; }; }) => Promise<{ date: string; sliValue: number; }[]>; } & ", { "pluginId": "observability", "scope": "server", @@ -10323,40 +10435,68 @@ "<{ metrics: ", "ArrayC", "<", + "UnionC", + "<[", "IntersectionC", "<[", "TypeC", "<{ name: ", "StringC", "; aggregation: ", - "KeyofC", - "<{ sum: boolean; }>; field: ", + "LiteralC", + "<\"sum\">; field: ", "StringC", "; }>, ", "PartialC", "<{ filter: ", "StringC", - "; }>]>>; equation: ", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>>; equation: ", "StringC", "; }>; total: ", "TypeC", "<{ metrics: ", "ArrayC", "<", + "UnionC", + "<[", "IntersectionC", "<[", "TypeC", "<{ name: ", "StringC", "; aggregation: ", - "KeyofC", - "<{ sum: boolean; }>; field: ", + "LiteralC", + "<\"sum\">; field: ", "StringC", "; }>, ", "PartialC", "<{ filter: ", "StringC", - "; }>]>>; equation: ", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>>; equation: ", "StringC", "; }>; timestampField: ", "StringC", @@ -10606,7 +10746,7 @@ "section": "def-server.ObservabilityRouteHandlerResources", "text": "ObservabilityRouteHandlerResources" }, - " & { params: { path: { id: string; }; body: { name?: string | undefined; description?: string | undefined; indicator?: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; } | undefined; timeWindow?: { duration: ", + " & { params: { path: { id: string; }; body: { name?: string | undefined; description?: string | undefined; indicator?: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; } | undefined; timeWindow?: { duration: ", { "pluginId": "@kbn/slo-schema", "scope": "common", @@ -10646,7 +10786,7 @@ "section": "def-common.Duration", "text": "Duration" }, - " | undefined; } | undefined; tags?: string[] | undefined; groupBy?: string | undefined; }; }; }) => Promise<{ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { syncDelay: string; frequency: string; }; enabled: boolean; tags: string[]; groupBy: string; createdAt: string; updatedAt: string; } & { instanceId?: string | undefined; }>; } & ", + " | undefined; } | undefined; tags?: string[] | undefined; groupBy?: string | undefined; }; }; }) => Promise<{ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { syncDelay: string; frequency: string; }; enabled: boolean; tags: string[]; groupBy: string; createdAt: string; updatedAt: string; } & { instanceId?: string | undefined; }>; } & ", { "pluginId": "observability", "scope": "server", @@ -10680,7 +10820,7 @@ "section": "def-server.ObservabilityRouteHandlerResources", "text": "ObservabilityRouteHandlerResources" }, - " & { params: { path: { id: string; }; } & { query?: { instanceId?: string | undefined; } | undefined; }; }) => Promise<{ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { syncDelay: string; frequency: string; }; enabled: boolean; tags: string[]; groupBy: string; createdAt: string; updatedAt: string; } & { instanceId?: string | undefined; } & { summary: { status: \"HEALTHY\" | \"NO_DATA\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; }; }>; } & ", + " & { params: { path: { id: string; }; } & { query?: { instanceId?: string | undefined; } | undefined; }; }) => Promise<{ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { syncDelay: string; frequency: string; }; enabled: boolean; tags: string[]; groupBy: string; createdAt: string; updatedAt: string; } & { instanceId?: string | undefined; } & { summary: { status: \"HEALTHY\" | \"NO_DATA\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; }; }>; } & ", { "pluginId": "observability", "scope": "server", @@ -10722,7 +10862,7 @@ "section": "def-server.ObservabilityRouteHandlerResources", "text": "ObservabilityRouteHandlerResources" }, - " & { params?: { query?: { kqlQuery?: string | undefined; page?: string | undefined; perPage?: string | undefined; sortBy?: \"status\" | \"error_budget_consumed\" | \"error_budget_remaining\" | \"sli_value\" | undefined; sortDirection?: \"asc\" | \"desc\" | undefined; } | undefined; } | undefined; }) => Promise<{ page: number; perPage: number; total: number; results: ({ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { syncDelay: string; frequency: string; }; enabled: boolean; tags: string[]; groupBy: string; createdAt: string; updatedAt: string; } & { instanceId?: string | undefined; } & { summary: { status: \"HEALTHY\" | \"NO_DATA\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; }; })[]; }>; } & ", + " & { params?: { query?: { kqlQuery?: string | undefined; page?: string | undefined; perPage?: string | undefined; sortBy?: \"status\" | \"error_budget_consumed\" | \"error_budget_remaining\" | \"sli_value\" | undefined; sortDirection?: \"asc\" | \"desc\" | undefined; } | undefined; } | undefined; }) => Promise<{ page: number; perPage: number; total: number; results: ({ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { syncDelay: string; frequency: string; }; enabled: boolean; tags: string[]; groupBy: string; createdAt: string; updatedAt: string; } & { instanceId?: string | undefined; } & { summary: { status: \"HEALTHY\" | \"NO_DATA\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; }; })[]; }>; } & ", { "pluginId": "observability", "scope": "server", @@ -10744,7 +10884,7 @@ "section": "def-server.ObservabilityRouteHandlerResources", "text": "ObservabilityRouteHandlerResources" }, - " & { params: { query: { search: string; }; }; }) => Promise<({ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { syncDelay: string; frequency: string; }; enabled: boolean; tags: string[]; groupBy: string; createdAt: string; updatedAt: string; } & { instanceId?: string | undefined; })[]>; } & ", + " & { params: { query: { search: string; }; }; }) => Promise<({ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { syncDelay: string; frequency: string; }; enabled: boolean; tags: string[]; groupBy: string; createdAt: string; updatedAt: string; } & { instanceId?: string | undefined; })[]>; } & ", { "pluginId": "observability", "scope": "server", @@ -11003,40 +11143,68 @@ "<{ metrics: ", "ArrayC", "<", + "UnionC", + "<[", "IntersectionC", "<[", "TypeC", "<{ name: ", "StringC", "; aggregation: ", - "KeyofC", - "<{ sum: boolean; }>; field: ", + "LiteralC", + "<\"sum\">; field: ", "StringC", "; }>, ", "PartialC", "<{ filter: ", "StringC", - "; }>]>>; equation: ", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>>; equation: ", "StringC", "; }>; total: ", "TypeC", "<{ metrics: ", "ArrayC", "<", + "UnionC", + "<[", "IntersectionC", "<[", "TypeC", "<{ name: ", "StringC", "; aggregation: ", - "KeyofC", - "<{ sum: boolean; }>; field: ", + "LiteralC", + "<\"sum\">; field: ", "StringC", "; }>, ", "PartialC", "<{ filter: ", "StringC", - "; }>]>>; equation: ", + "; }>]>, ", + "IntersectionC", + "<[", + "TypeC", + "<{ name: ", + "StringC", + "; aggregation: ", + "LiteralC", + "<\"doc_count\">; }>, ", + "PartialC", + "<{ filter: ", + "StringC", + "; }>]>]>>; equation: ", "StringC", "; }>; timestampField: ", "StringC", @@ -11290,7 +11458,7 @@ "section": "def-server.ObservabilityRouteHandlerResources", "text": "ObservabilityRouteHandlerResources" }, - " & { params: { body: { name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; total: { metrics: ({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; })[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: ", + " & { params: { body: { name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string; total: string; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | undefined; }); } & { filter?: string | undefined; }; }; timeWindow: { duration: ", { "pluginId": "@kbn/slo-schema", "scope": "common", diff --git a/api_docs/observability.mdx b/api_docs/observability.mdx index d3b50e14771eb5..3628c75b6ceeda 100644 --- a/api_docs/observability.mdx +++ b/api_docs/observability.mdx @@ -8,14 +8,14 @@ slug: /kibana-dev-docs/api/observability title: "observability" image: https://source.unsplash.com/400x175/?github description: API docs for the observability plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observability'] --- import observabilityObj from './observability.devdocs.json'; -Contact [@elastic/actionable-observability](https://github.com/orgs/elastic/teams/actionable-observability) for questions regarding this plugin. +Contact [@elastic/obs-ux-management-team](https://github.com/orgs/elastic/teams/obs-ux-management-team) for questions regarding this plugin. **Code health stats** diff --git a/api_docs/observability_a_i_assistant.mdx b/api_docs/observability_a_i_assistant.mdx index e7f247ab540b5b..3af073c1f66f04 100644 --- a/api_docs/observability_a_i_assistant.mdx +++ b/api_docs/observability_a_i_assistant.mdx @@ -8,14 +8,14 @@ slug: /kibana-dev-docs/api/observabilityAIAssistant title: "observabilityAIAssistant" image: https://source.unsplash.com/400x175/?github description: API docs for the observabilityAIAssistant plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityAIAssistant'] --- import observabilityAIAssistantObj from './observability_a_i_assistant.devdocs.json'; -Contact [@elastic/obs-ai-assistant](https://github.com/orgs/elastic/teams/obs-ai-assistant) for questions regarding this plugin. +Contact [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs-knowledge-team) for questions regarding this plugin. **Code health stats** diff --git a/api_docs/observability_log_explorer.mdx b/api_docs/observability_log_explorer.mdx index 7b6ec386bc7021..156ff2060adf6c 100644 --- a/api_docs/observability_log_explorer.mdx +++ b/api_docs/observability_log_explorer.mdx @@ -8,14 +8,14 @@ slug: /kibana-dev-docs/api/observabilityLogExplorer title: "observabilityLogExplorer" image: https://source.unsplash.com/400x175/?github description: API docs for the observabilityLogExplorer plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityLogExplorer'] --- import observabilityLogExplorerObj from './observability_log_explorer.devdocs.json'; This plugin exposes and registers observability log consumption features. -Contact [@elastic/infra-monitoring-ui](https://github.com/orgs/elastic/teams/infra-monitoring-ui) for questions regarding this plugin. +Contact [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) for questions regarding this plugin. **Code health stats** diff --git a/api_docs/observability_onboarding.mdx b/api_docs/observability_onboarding.mdx index 84a965b18536d9..395daffaed93d1 100644 --- a/api_docs/observability_onboarding.mdx +++ b/api_docs/observability_onboarding.mdx @@ -8,14 +8,14 @@ slug: /kibana-dev-docs/api/observabilityOnboarding title: "observabilityOnboarding" image: https://source.unsplash.com/400x175/?github description: API docs for the observabilityOnboarding plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityOnboarding'] --- import observabilityOnboardingObj from './observability_onboarding.devdocs.json'; -Contact [@elastic/apm-ui](https://github.com/orgs/elastic/teams/apm-ui) for questions regarding this plugin. +Contact [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) for questions regarding this plugin. **Code health stats** diff --git a/api_docs/observability_shared.mdx b/api_docs/observability_shared.mdx index a21c8f36c672e9..49b4b7060b8f74 100644 --- a/api_docs/observability_shared.mdx +++ b/api_docs/observability_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observabilityShared title: "observabilityShared" image: https://source.unsplash.com/400x175/?github description: API docs for the observabilityShared plugin -date: 2023-11-06 +date: 2023-11-10 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 459e897346a0fc..48b66b1e51692d 100644 --- a/api_docs/osquery.mdx +++ b/api_docs/osquery.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/osquery title: "osquery" image: https://source.unsplash.com/400x175/?github description: API docs for the osquery plugin -date: 2023-11-06 +date: 2023-11-10 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 ca05cb66654591..57e905be4b5af7 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: 2023-11-06 +date: 2023-11-10 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 158868e57b6a06..898241c4b14130 100644 --- a/api_docs/plugin_directory.mdx +++ b/api_docs/plugin_directory.mdx @@ -7,7 +7,7 @@ id: kibDevDocsPluginDirectory slug: /kibana-dev-docs/api-meta/plugin-api-directory title: Directory description: Directory of public APIs available through plugins or packages. -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -21,7 +21,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | API Count | Any Count | Missing comments | Missing exports | |--------------|----------|-----------------|--------| -| 76048 | 232 | 65029 | 1592 | +| 76089 | 233 | 65049 | 1597 | ## Plugin Directory @@ -31,9 +31,9 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/appex-sharedux @elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/appex-sharedux ) | - | 17 | 1 | 15 | 2 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | AIOps plugin maintained by ML team. | 69 | 1 | 4 | 1 | | | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 809 | 1 | 778 | 50 | -| | [@elastic/apm-ui](https://github.com/orgs/elastic/teams/apm-ui) | The user interface for Elastic APM | 29 | 0 | 29 | 120 | -| | [@elastic/apm-ui](https://github.com/orgs/elastic/teams/apm-ui) | - | 9 | 0 | 9 | 0 | -| | [@elastic/infra-monitoring-ui](https://github.com/orgs/elastic/teams/infra-monitoring-ui) | Asset manager plugin for entity assets (inventory, topology, etc) | 9 | 0 | 9 | 2 | +| | [@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 | 120 | +| | [@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 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 9 | 0 | 9 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Considering using bfetch capabilities when fetching large amounts of data. This services supports batching HTTP requests and streaming responses back. | 91 | 1 | 75 | 2 | | | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds Canvas application to Kibana | 9 | 0 | 8 | 3 | @@ -75,7 +75,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | The Event Annotation service contains expressions for event annotations | 201 | 0 | 201 | 6 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | The listing page for event annotations. | 15 | 0 | 15 | 0 | | | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 111 | 0 | 111 | 11 | -| | [@elastic/uptime](https://github.com/orgs/elastic/teams/uptime) | - | 131 | 1 | 131 | 14 | +| | [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) | - | 131 | 1 | 131 | 14 | | | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds 'error' renderer to expressions | 17 | 0 | 15 | 2 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | Expression Gauge plugin adds a `gauge` renderer and function to the expression plugin. The renderer will display the `gauge` chart. | 59 | 0 | 59 | 2 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | Expression Heatmap plugin adds a `heatmap` renderer and function to the expression plugin. The renderer will display the `heatmap` chart. | 112 | 0 | 108 | 2 | @@ -95,7 +95,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-gis](https://github.com/orgs/elastic/teams/kibana-gis) | The file upload plugin contains components and services for uploading a file, analyzing its data, and then importing the data into an Elasticsearch index. Supported file types include CSV, TSV, newline-delimited JSON and GeoJSON. | 59 | 0 | 59 | 2 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | File upload, download, sharing, and serving over HTTP implementation in Kibana. | 239 | 0 | 24 | 9 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Simple UI for managing files in Kibana | 2 | 0 | 2 | 0 | -| | [@elastic/fleet](https://github.com/orgs/elastic/teams/fleet) | - | 1209 | 3 | 1091 | 45 | +| | [@elastic/fleet](https://github.com/orgs/elastic/teams/fleet) | - | 1212 | 3 | 1094 | 46 | | 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 | @@ -106,8 +106,8 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 149 | 0 | 109 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Image embeddable | 3 | 0 | 3 | 1 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 4 | 0 | 4 | 0 | -| | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 204 | 0 | 199 | 4 | -| | [@elastic/infra-monitoring-ui](https://github.com/orgs/elastic/teams/infra-monitoring-ui) | This plugin visualizes data from Filebeat and Metricbeat, and integrates with other Observability solutions | 35 | 0 | 32 | 9 | +| | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 206 | 0 | 201 | 4 | +| | [@elastic/infra-monitoring-ui](https://github.com/orgs/elastic/teams/infra-monitoring-ui) | This plugin visualizes data from Filebeat and Metricbeat, and integrates with other Observability solutions | 32 | 0 | 29 | 8 | | ingestPipelines | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 0 | 0 | 0 | 0 | | inputControlVis | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds Input Control visualization to Kibana | 0 | 0 | 0 | 0 | | | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | - | 123 | 2 | 96 | 4 | @@ -123,30 +123,30 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 117 | 0 | 42 | 10 | | | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | A dashboard panel for creating links to dashboards or external links. | 64 | 0 | 62 | 7 | | | [@elastic/security-detection-engine](https://github.com/orgs/elastic/teams/security-detection-engine) | - | 224 | 0 | 96 | 51 | -| | [@elastic/infra-monitoring-ui](https://github.com/orgs/elastic/teams/infra-monitoring-ui) | This plugin provides a LogExplorer component using the Discover customization framework, offering several affordances specifically designed for log consumption. | 22 | 0 | 22 | 7 | -| | [@elastic/infra-monitoring-ui](https://github.com/orgs/elastic/teams/infra-monitoring-ui) | Exposes the shared components and APIs to access and visualize logs. | 279 | 10 | 264 | 27 | +| | [@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. | 26 | 0 | 26 | 8 | +| | [@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. | 287 | 11 | 272 | 28 | | 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 | -| | [@elastic/kibana-gis](https://github.com/orgs/elastic/teams/kibana-gis) | - | 259 | 0 | 258 | 28 | +| | [@elastic/kibana-gis](https://github.com/orgs/elastic/teams/kibana-gis) | - | 260 | 0 | 259 | 29 | | | [@elastic/kibana-gis](https://github.com/orgs/elastic/teams/kibana-gis) | - | 68 | 0 | 68 | 0 | -| | [@elastic/infra-monitoring-ui](https://github.com/orgs/elastic/teams/infra-monitoring-ui) | Exposes utilities for accessing metrics data | 104 | 8 | 104 | 4 | +| | [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs-knowledge-team) | Exposes utilities for accessing metrics data | 104 | 8 | 104 | 4 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | This plugin provides access to the machine learning features provided by Elastic. | 150 | 3 | 64 | 33 | -| | [@elastic/infra-monitoring-ui](https://github.com/orgs/elastic/teams/infra-monitoring-ui) | - | 15 | 3 | 13 | 1 | -| | [@elastic/infra-monitoring-ui](https://github.com/orgs/elastic/teams/infra-monitoring-ui) | - | 9 | 0 | 9 | 0 | +| | [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) | - | 15 | 3 | 13 | 1 | +| | [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) | - | 9 | 0 | 9 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 34 | 0 | 34 | 2 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 17 | 0 | 17 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 3 | 0 | 3 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 2 | 0 | 2 | 1 | -| | [@elastic/actionable-observability](https://github.com/orgs/elastic/teams/actionable-observability) | - | 580 | 2 | 571 | 17 | -| | [@elastic/obs-ai-assistant](https://github.com/orgs/elastic/teams/obs-ai-assistant) | - | 42 | 0 | 39 | 7 | -| | [@elastic/infra-monitoring-ui](https://github.com/orgs/elastic/teams/infra-monitoring-ui) | This plugin exposes and registers observability log consumption features. | 15 | 0 | 15 | 1 | -| | [@elastic/apm-ui](https://github.com/orgs/elastic/teams/apm-ui) | - | 14 | 0 | 14 | 0 | +| | [@elastic/obs-ux-management-team](https://github.com/orgs/elastic/teams/obs-ux-management-team) | - | 580 | 2 | 571 | 17 | +| | [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs-knowledge-team) | - | 42 | 0 | 39 | 7 | +| | [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) | This plugin exposes and registers observability log consumption features. | 15 | 0 | 15 | 1 | +| | [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) | - | 14 | 0 | 14 | 0 | | | [@elastic/observability-ui](https://github.com/orgs/elastic/teams/observability-ui) | - | 292 | 1 | 289 | 15 | | | [@elastic/security-defend-workflows](https://github.com/orgs/elastic/teams/security-defend-workflows) | - | 24 | 0 | 24 | 7 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 2 | 0 | 2 | 0 | | | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | The Presentation Utility Plugin is a set of common, shared components and toolkits for solutions within the Presentation space, (e.g. Dashboards, Canvas). | 219 | 2 | 164 | 11 | -| | [@elastic/profiling-ui](https://github.com/orgs/elastic/teams/profiling-ui) | - | 16 | 1 | 16 | 0 | -| | [@elastic/profiling-ui](https://github.com/orgs/elastic/teams/profiling-ui) | - | 22 | 0 | 22 | 7 | +| | [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) | - | 16 | 1 | 16 | 0 | +| | [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) | - | 22 | 0 | 22 | 7 | | | [@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. | 42 | 0 | 22 | 5 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 21 | 0 | 21 | 0 | @@ -162,7 +162,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-reporting-services](https://github.com/orgs/elastic/teams/kibana-reporting-services) | Kibana Screenshotting Plugin | 27 | 0 | 8 | 5 | | searchprofiler | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 0 | 0 | 0 | 0 | | | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | This plugin provides authentication and authorization features, and exposes functionality to understand the capabilities of the currently authenticated user. | 270 | 0 | 87 | 3 | -| | [@elastic/security-solution](https://github.com/orgs/elastic/teams/security-solution) | - | 173 | 0 | 106 | 35 | +| | [@elastic/security-solution](https://github.com/orgs/elastic/teams/security-solution) | - | 174 | 0 | 106 | 35 | | | [@elastic/security-solution](https://github.com/orgs/elastic/teams/security-solution) | ESS customizations for Security Solution. | 6 | 0 | 6 | 0 | | | [@elastic/security-solution](https://github.com/orgs/elastic/teams/security-solution) | Serverless customizations for security. | 7 | 0 | 7 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | The core Serverless plugin, providing APIs to Serverless Project plugins. | 19 | 0 | 18 | 0 | @@ -174,7 +174,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@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 | | | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 4 | 0 | 4 | 1 | -| synthetics | [@elastic/uptime](https://github.com/orgs/elastic/teams/uptime) | This plugin visualizes data from Synthetics and Heartbeat, and integrates with other Observability solutions. | 0 | 0 | 0 | 0 | +| synthetics | [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) | This plugin visualizes data from Synthetics and Heartbeat, and integrates with other Observability solutions. | 0 | 0 | 0 | 0 | | | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 96 | 0 | 53 | 6 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 45 | 0 | 1 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 31 | 0 | 26 | 6 | @@ -192,11 +192,11 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@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 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | Contains all the key functionality of Kibana's unified search experience.Contains all the key functionality of Kibana's unified search experience. | 148 | 2 | 110 | 23 | | upgradeAssistant | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 0 | 0 | 0 | 0 | -| | [@elastic/uptime](https://github.com/orgs/elastic/teams/uptime) | This plugin visualizes data from Heartbeat, and integrates with other Observability solutions. | 1 | 0 | 1 | 0 | +| | [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) | This plugin visualizes data from Heartbeat, and integrates with other Observability solutions. | 1 | 0 | 1 | 0 | | urlDrilldown | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Adds drilldown implementations to Kibana | 0 | 0 | 0 | 0 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 12 | 0 | 12 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 55 | 0 | 16 | 2 | -| | [@elastic/uptime](https://github.com/orgs/elastic/teams/uptime) | - | 2 | 0 | 2 | 0 | +| | [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) | - | 2 | 0 | 2 | 0 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | The default editor used in most aggregation-based visualizations. | 56 | 0 | 49 | 4 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | Contains the gauge chart implementation using the elastic-charts library. The goal is to eventually deprecate the old implementation and keep only this. Until then, the library used is defined by the Legacy charts library advanced setting. | 7 | 0 | 7 | 2 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | Contains the heatmap implementation using the elastic-charts library. The goal is to eventually deprecate the old implementation and keep only this. Until then, the library used is defined by the Legacy heatmap charts library advanced setting. | 3 | 0 | 3 | 2 | @@ -232,9 +232,9 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 21 | 0 | 0 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 18 | 0 | 2 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 17 | 0 | 17 | 0 | -| | [@elastic/apm-ui](https://github.com/orgs/elastic/teams/apm-ui) | - | 31 | 0 | 31 | 7 | -| | [@elastic/apm-ui](https://github.com/orgs/elastic/teams/apm-ui) | - | 180 | 0 | 180 | 26 | -| | [@elastic/apm-ui](https://github.com/orgs/elastic/teams/apm-ui) | - | 11 | 0 | 11 | 0 | +| | [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) | - | 31 | 0 | 31 | 7 | +| | [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) | - | 181 | 0 | 181 | 27 | +| | [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) | - | 11 | 0 | 11 | 0 | | | [@elastic/kibana-qa](https://github.com/orgs/elastic/teams/kibana-qa) | - | 12 | 0 | 12 | 0 | | | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 19 | 0 | 16 | 0 | | | [@elastic/security-threat-hunting-explore](https://github.com/orgs/elastic/teams/security-threat-hunting-explore) | - | 62 | 1 | 44 | 3 | @@ -273,7 +273,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 3 | 0 | 3 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 3 | 0 | 3 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 5 | 0 | 0 | 0 | -| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 16 | 0 | 7 | 0 | +| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 20 | 0 | 7 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 6 | 0 | 6 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 165 | 0 | 70 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 3 | 0 | 3 | 0 | @@ -415,7 +415,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | - | 3 | 0 | 3 | 0 | | | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | - | 13 | 0 | 7 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 10 | 0 | 10 | 0 | -| | [@elastic/infra-monitoring-ui](https://github.com/orgs/elastic/teams/infra-monitoring-ui) | - | 19 | 0 | 17 | 6 | +| | [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) | - | 19 | 0 | 17 | 6 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 2 | 0 | 2 | 0 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 14 | 0 | 9 | 0 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 44 | 0 | 43 | 0 | @@ -423,7 +423,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 5 | 0 | 5 | 0 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 4 | 0 | 4 | 0 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 3 | 0 | 3 | 0 | -| | [@elastic/apm-ui](https://github.com/orgs/elastic/teams/apm-ui) | - | 26 | 0 | 16 | 0 | +| | [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) | - | 26 | 0 | 16 | 0 | | | [@elastic/enterprise-search-frontend](https://github.com/orgs/elastic/teams/enterprise-search-frontend) | - | 4 | 0 | 4 | 0 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 8 | 0 | 8 | 0 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 8 | 0 | 8 | 0 | @@ -440,7 +440,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 19 | 0 | 11 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 35125 | 0 | 34718 | 0 | | | [@elastic/security-threat-hunting-investigations](https://github.com/orgs/elastic/teams/security-threat-hunting-investigations) | - | 13 | 0 | 5 | 0 | -| | [@elastic/security-solution](https://github.com/orgs/elastic/teams/security-solution) | - | 95 | 0 | 75 | 6 | +| | [@elastic/security-solution](https://github.com/orgs/elastic/teams/security-solution) | - | 96 | 0 | 76 | 6 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 48 | 0 | 33 | 7 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 27 | 0 | 14 | 1 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 7 | 0 | 3 | 0 | @@ -467,15 +467,15 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 51 | 0 | 48 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 61 | 0 | 1 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 47 | 0 | 40 | 0 | -| | [@elastic/actionable-observability](https://github.com/orgs/elastic/teams/actionable-observability) | - | 9 | 0 | 9 | 0 | +| | [@elastic/obs-ux-management-team](https://github.com/orgs/elastic/teams/obs-ux-management-team) | - | 9 | 0 | 9 | 0 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 52 | 12 | 43 | 0 | -| | [@elastic/apm-ui](https://github.com/orgs/elastic/teams/apm-ui) | - | 52 | 0 | 52 | 4 | +| | [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs-knowledge-team) | - | 52 | 0 | 52 | 4 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 13 | 0 | 13 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 85 | 0 | 77 | 6 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 41 | 2 | 35 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 108 | 0 | 107 | 0 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 7 | 0 | 5 | 0 | -| | [@elastic/infra-monitoring-ui](https://github.com/orgs/elastic/teams/infra-monitoring-ui) | - | 158 | 0 | 155 | 0 | +| | [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) | - | 158 | 0 | 155 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 27 | 0 | 1 | 2 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 8 | 0 | 8 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 6 | 0 | 1 | 1 | @@ -491,7 +491,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 81 | 0 | 3 | 0 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 54 | 0 | 6 | 0 | | | [@elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/platform-deployment-management) | - | 2 | 0 | 0 | 0 | -| | [@elastic/kibana-gis](https://github.com/orgs/elastic/teams/kibana-gis) | - | 582 | 1 | 1 | 0 | +| | [@elastic/kibana-gis](https://github.com/orgs/elastic/teams/kibana-gis) | - | 592 | 1 | 1 | 0 | | | [@elastic/kibana-gis](https://github.com/orgs/elastic/teams/kibana-gis) | - | 2 | 0 | 2 | 0 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 99 | 2 | 0 | 0 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 206 | 3 | 1 | 0 | @@ -527,7 +527,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-performance-testing](https://github.com/orgs/elastic/teams/kibana-performance-testing) | - | 3 | 0 | 3 | 1 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 1 | 0 | 1 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 1 | 0 | 1 | 0 | -| | [@elastic/profiling-ui](https://github.com/orgs/elastic/teams/profiling-ui) | - | 171 | 0 | 28 | 0 | +| | [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) | - | 171 | 0 | 28 | 0 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 13 | 0 | 7 | 0 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 22 | 0 | 9 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 8 | 0 | 2 | 0 | @@ -544,7 +544,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | A component for creating resizable layouts containing a fixed width panel and a flexible panel, with support for horizontal and vertical layouts. | 18 | 0 | 5 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 13 | 2 | 8 | 0 | | | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 16 | 0 | 16 | 1 | -| | [@elastic/security-detections-response](https://github.com/orgs/elastic/teams/security-detections-response) | - | 115 | 0 | 112 | 0 | +| | [@elastic/security-detections-response](https://github.com/orgs/elastic/teams/security-detections-response) | - | 116 | 0 | 113 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 2 | 0 | 2 | 0 | | | [@elastic/enterprise-search-frontend](https://github.com/orgs/elastic/teams/enterprise-search-frontend) | - | 68 | 0 | 68 | 0 | | | [@elastic/enterprise-search-frontend](https://github.com/orgs/elastic/teams/enterprise-search-frontend) | - | 2211 | 0 | 2211 | 0 | @@ -572,20 +572,20 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/security-detection-engine](https://github.com/orgs/elastic/teams/security-detection-engine) | - | 120 | 0 | 116 | 0 | | | [@elastic/security-detection-engine](https://github.com/orgs/elastic/teams/security-detection-engine) | - | 37 | 0 | 32 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 54 | 0 | 51 | 1 | -| | [@elastic/apm-ui](https://github.com/orgs/elastic/teams/apm-ui) | - | 31 | 0 | 30 | 1 | +| | [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs-knowledge-team) | - | 31 | 0 | 30 | 1 | | | [@elastic/appex-sharedux @elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/appex-sharedux ) | - | 1 | 0 | 1 | 0 | -| | [@elastic/appex-sharedux @elastic/apm-ui @elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/appex-sharedux ) | - | 1 | 0 | 1 | 0 | +| | [@elastic/appex-sharedux @elastic/platform-deployment-management @elastic/obs-ux-management-team](https://github.com/orgs/elastic/teams/appex-sharedux ) | - | 1 | 0 | 1 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 10 | 0 | 7 | 1 | | | [@elastic/enterprise-search-frontend @elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/enterprise-search-frontend ) | - | 1 | 0 | 1 | 0 | | | [@elastic/security-solution @elastic/platform-deployment-management](https://github.com/orgs/elastic/teams/security-solution ) | - | 1 | 0 | 1 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 2 | 0 | 0 | 0 | -| | [@elastic/apm-ui](https://github.com/orgs/elastic/teams/apm-ui) | - | 4 | 0 | 4 | 0 | +| | [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) | - | 4 | 0 | 4 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 3 | 0 | 2 | 2 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 8 | 0 | 2 | 1 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 28 | 0 | 10 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 10 | 0 | 4 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 32 | 0 | 28 | 0 | -| | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 60 | 0 | 49 | 4 | +| | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 60 | 0 | 49 | 5 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 6 | 0 | 2 | 1 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 5 | 0 | 4 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 3 | 0 | 2 | 0 | @@ -618,7 +618,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 2 | 0 | 0 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 15 | 0 | 4 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 11 | 0 | 3 | 0 | -| | [@elastic/actionable-observability](https://github.com/orgs/elastic/teams/actionable-observability) | - | 129 | 0 | 126 | 0 | +| | [@elastic/obs-ux-management-team](https://github.com/orgs/elastic/teams/obs-ux-management-team) | - | 131 | 0 | 128 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 20 | 0 | 12 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 102 | 2 | 65 | 1 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 4 | 0 | 2 | 0 | @@ -631,7 +631,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 22 | 0 | 21 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 72 | 0 | 55 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 39 | 0 | 25 | 1 | -| | [@elastic/apm-ui](https://github.com/orgs/elastic/teams/apm-ui) | - | 86 | 0 | 86 | 1 | +| | [@elastic/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) | - | 49 | 0 | 35 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 53 | 0 | 44 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 7 | 0 | 6 | 0 | @@ -639,13 +639,13 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 10 | 0 | 7 | 7 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | Contains functionality for the field list and field stats which can be integrated into apps | 285 | 0 | 261 | 9 | | | [@elastic/security-threat-hunting-investigations](https://github.com/orgs/elastic/teams/security-threat-hunting-investigations) | - | 4 | 0 | 0 | 0 | -| | [@elastic/infra-monitoring-ui](https://github.com/orgs/elastic/teams/infra-monitoring-ui) | - | 3 | 0 | 2 | 1 | +| | [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) | - | 3 | 0 | 2 | 1 | | | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | - | 80 | 1 | 21 | 2 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 37 | 0 | 16 | 1 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 2 | 0 | 2 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 24 | 0 | 14 | 0 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 156 | 0 | 152 | 3 | -| | [@elastic/infra-monitoring-ui](https://github.com/orgs/elastic/teams/infra-monitoring-ui) | - | 12 | 0 | 12 | 0 | +| | [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) | - | 12 | 0 | 12 | 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) | - | 12 | 0 | 9 | 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_util.mdx b/api_docs/presentation_util.mdx index 365649ee540273..f58a021c502b5f 100644 --- a/api_docs/presentation_util.mdx +++ b/api_docs/presentation_util.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/presentationUtil title: "presentationUtil" image: https://source.unsplash.com/400x175/?github description: API docs for the presentationUtil plugin -date: 2023-11-06 +date: 2023-11-10 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 847452aa298cf4..cd7539fc60ab38 100644 --- a/api_docs/profiling.mdx +++ b/api_docs/profiling.mdx @@ -8,14 +8,14 @@ slug: /kibana-dev-docs/api/profiling title: "profiling" image: https://source.unsplash.com/400x175/?github description: API docs for the profiling plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'profiling'] --- import profilingObj from './profiling.devdocs.json'; -Contact [@elastic/profiling-ui](https://github.com/orgs/elastic/teams/profiling-ui) for questions regarding this plugin. +Contact [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) for questions regarding this plugin. **Code health stats** diff --git a/api_docs/profiling_data_access.mdx b/api_docs/profiling_data_access.mdx index 5a7a9b8a59d070..9e484291281bf2 100644 --- a/api_docs/profiling_data_access.mdx +++ b/api_docs/profiling_data_access.mdx @@ -8,14 +8,14 @@ slug: /kibana-dev-docs/api/profilingDataAccess title: "profilingDataAccess" image: https://source.unsplash.com/400x175/?github description: API docs for the profilingDataAccess plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'profilingDataAccess'] --- import profilingDataAccessObj from './profiling_data_access.devdocs.json'; -Contact [@elastic/profiling-ui](https://github.com/orgs/elastic/teams/profiling-ui) for questions regarding this plugin. +Contact [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) for questions regarding this plugin. **Code health stats** diff --git a/api_docs/remote_clusters.mdx b/api_docs/remote_clusters.mdx index def6616f8fb6b4..122eb47b5f1082 100644 --- a/api_docs/remote_clusters.mdx +++ b/api_docs/remote_clusters.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/remoteClusters title: "remoteClusters" image: https://source.unsplash.com/400x175/?github description: API docs for the remoteClusters plugin -date: 2023-11-06 +date: 2023-11-10 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 11d94c6d3061e1..7218c2d384f9a6 100644 --- a/api_docs/reporting.mdx +++ b/api_docs/reporting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/reporting title: "reporting" image: https://source.unsplash.com/400x175/?github description: API docs for the reporting plugin -date: 2023-11-06 +date: 2023-11-10 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 e79035731bdae1..80f6f81edc01a2 100644 --- a/api_docs/rollup.mdx +++ b/api_docs/rollup.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/rollup title: "rollup" image: https://source.unsplash.com/400x175/?github description: API docs for the rollup plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'rollup'] --- import rollupObj from './rollup.devdocs.json'; diff --git a/api_docs/rule_registry.mdx b/api_docs/rule_registry.mdx index de5b755011935e..2484623c379eaf 100644 --- a/api_docs/rule_registry.mdx +++ b/api_docs/rule_registry.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ruleRegistry title: "ruleRegistry" image: https://source.unsplash.com/400x175/?github description: API docs for the ruleRegistry plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ruleRegistry'] --- import ruleRegistryObj from './rule_registry.devdocs.json'; diff --git a/api_docs/runtime_fields.mdx b/api_docs/runtime_fields.mdx index 629c95c5d04ed4..73f40c7ef02bbf 100644 --- a/api_docs/runtime_fields.mdx +++ b/api_docs/runtime_fields.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/runtimeFields title: "runtimeFields" image: https://source.unsplash.com/400x175/?github description: API docs for the runtimeFields plugin -date: 2023-11-06 +date: 2023-11-10 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 15030d4ab31ab7..787d563cf833ff 100644 --- a/api_docs/saved_objects.mdx +++ b/api_docs/saved_objects.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjects title: "savedObjects" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjects plugin -date: 2023-11-06 +date: 2023-11-10 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 4cab98e4ac01d3..fe075a2d14aefc 100644 --- a/api_docs/saved_objects_finder.mdx +++ b/api_docs/saved_objects_finder.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsFinder title: "savedObjectsFinder" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsFinder plugin -date: 2023-11-06 +date: 2023-11-10 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 237d03dd841961..f9535fb5701374 100644 --- a/api_docs/saved_objects_management.mdx +++ b/api_docs/saved_objects_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsManagement title: "savedObjectsManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsManagement plugin -date: 2023-11-06 +date: 2023-11-10 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 ccaa377045d5b3..b176edd0697b31 100644 --- a/api_docs/saved_objects_tagging.mdx +++ b/api_docs/saved_objects_tagging.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsTagging title: "savedObjectsTagging" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsTagging plugin -date: 2023-11-06 +date: 2023-11-10 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 f5a7035a179b95..dfed121fe9d037 100644 --- a/api_docs/saved_objects_tagging_oss.mdx +++ b/api_docs/saved_objects_tagging_oss.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsTaggingOss title: "savedObjectsTaggingOss" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsTaggingOss plugin -date: 2023-11-06 +date: 2023-11-10 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 21ea73fc46c9a6..498512acef6343 100644 --- a/api_docs/saved_search.mdx +++ b/api_docs/saved_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedSearch title: "savedSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the savedSearch plugin -date: 2023-11-06 +date: 2023-11-10 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 50f37ecc7352a1..04d8a5f1f47be4 100644 --- a/api_docs/screenshot_mode.mdx +++ b/api_docs/screenshot_mode.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/screenshotMode title: "screenshotMode" image: https://source.unsplash.com/400x175/?github description: API docs for the screenshotMode plugin -date: 2023-11-06 +date: 2023-11-10 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 f1a634482ed94f..2f152b467d568d 100644 --- a/api_docs/screenshotting.mdx +++ b/api_docs/screenshotting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/screenshotting title: "screenshotting" image: https://source.unsplash.com/400x175/?github description: API docs for the screenshotting plugin -date: 2023-11-06 +date: 2023-11-10 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 422b8e33327593..bc227e0d57c92d 100644 --- a/api_docs/security.mdx +++ b/api_docs/security.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/security title: "security" image: https://source.unsplash.com/400x175/?github description: API docs for the security plugin -date: 2023-11-06 +date: 2023-11-10 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 59b53883cd01c6..ce6fc3937f3ecf 100644 --- a/api_docs/security_solution.devdocs.json +++ b/api_docs/security_solution.devdocs.json @@ -101,7 +101,7 @@ "label": "experimentalFeatures", "description": [], "signature": [ - "{ readonly tGridEnabled: boolean; readonly tGridEventRenderedViewEnabled: boolean; readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly chartEmbeddablesEnabled: boolean; readonly donutChartEmbeddablesEnabled: boolean; readonly alertsPreviewChartEmbeddablesEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly insightsRelatedAlertsByProcessAncestry: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly socTrendsEnabled: boolean; readonly responseActionsEnabled: boolean; readonly endpointResponseActionsEnabled: boolean; readonly responseActionUploadEnabled: boolean; readonly alertsPageChartsEnabled: boolean; readonly alertTypeEnabled: boolean; readonly alertsPageFiltersEnabled: boolean; readonly assistantModelEvaluation: boolean; readonly newUserDetailsFlyout: boolean; readonly riskScoringPersistence: boolean; readonly riskScoringRoutesEnabled: boolean; readonly esqlRulesDisabled: boolean; readonly protectionUpdatesEnabled: boolean; }" + "{ readonly tGridEnabled: boolean; readonly tGridEventRenderedViewEnabled: boolean; readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly chartEmbeddablesEnabled: boolean; readonly donutChartEmbeddablesEnabled: boolean; readonly alertsPreviewChartEmbeddablesEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly insightsRelatedAlertsByProcessAncestry: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly socTrendsEnabled: boolean; readonly responseActionsEnabled: boolean; readonly endpointResponseActionsEnabled: boolean; readonly responseActionUploadEnabled: boolean; readonly alertsPageChartsEnabled: boolean; readonly alertTypeEnabled: boolean; readonly alertsPageFiltersEnabled: boolean; readonly assistantModelEvaluation: boolean; readonly newUserDetailsFlyout: boolean; readonly riskScoringPersistence: boolean; readonly riskScoringRoutesEnabled: boolean; readonly esqlRulesDisabled: boolean; readonly protectionUpdatesEnabled: boolean; readonly disableTimelineSaveTour: boolean; }" ], "path": "x-pack/plugins/security_solution/public/plugin.tsx", "deprecated": false, @@ -329,7 +329,7 @@ "label": "stop", "description": [], "signature": [ - "() => {}" + "() => void" ], "path": "x-pack/plugins/security_solution/public/plugin.tsx", "deprecated": false, @@ -494,7 +494,7 @@ "\nExperimental flag needed to enable the link" ], "signature": [ - "\"tGridEnabled\" | \"tGridEventRenderedViewEnabled\" | \"excludePoliciesInFilterEnabled\" | \"kubernetesEnabled\" | \"chartEmbeddablesEnabled\" | \"donutChartEmbeddablesEnabled\" | \"alertsPreviewChartEmbeddablesEnabled\" | \"previewTelemetryUrlEnabled\" | \"insightsRelatedAlertsByProcessAncestry\" | \"extendedRuleExecutionLoggingEnabled\" | \"socTrendsEnabled\" | \"responseActionsEnabled\" | \"endpointResponseActionsEnabled\" | \"responseActionUploadEnabled\" | \"alertsPageChartsEnabled\" | \"alertTypeEnabled\" | \"alertsPageFiltersEnabled\" | \"assistantModelEvaluation\" | \"newUserDetailsFlyout\" | \"riskScoringPersistence\" | \"riskScoringRoutesEnabled\" | \"esqlRulesDisabled\" | \"protectionUpdatesEnabled\" | undefined" + "\"tGridEnabled\" | \"tGridEventRenderedViewEnabled\" | \"excludePoliciesInFilterEnabled\" | \"kubernetesEnabled\" | \"chartEmbeddablesEnabled\" | \"donutChartEmbeddablesEnabled\" | \"alertsPreviewChartEmbeddablesEnabled\" | \"previewTelemetryUrlEnabled\" | \"insightsRelatedAlertsByProcessAncestry\" | \"extendedRuleExecutionLoggingEnabled\" | \"socTrendsEnabled\" | \"responseActionsEnabled\" | \"endpointResponseActionsEnabled\" | \"responseActionUploadEnabled\" | \"alertsPageChartsEnabled\" | \"alertTypeEnabled\" | \"alertsPageFiltersEnabled\" | \"assistantModelEvaluation\" | \"newUserDetailsFlyout\" | \"riskScoringPersistence\" | \"riskScoringRoutesEnabled\" | \"esqlRulesDisabled\" | \"protectionUpdatesEnabled\" | \"disableTimelineSaveTour\" | undefined" ], "path": "x-pack/plugins/security_solution/public/common/links/types.ts", "deprecated": false, @@ -574,7 +574,7 @@ "\nExperimental flag needed to disable the link. Opposite of experimentalKey" ], "signature": [ - "\"tGridEnabled\" | \"tGridEventRenderedViewEnabled\" | \"excludePoliciesInFilterEnabled\" | \"kubernetesEnabled\" | \"chartEmbeddablesEnabled\" | \"donutChartEmbeddablesEnabled\" | \"alertsPreviewChartEmbeddablesEnabled\" | \"previewTelemetryUrlEnabled\" | \"insightsRelatedAlertsByProcessAncestry\" | \"extendedRuleExecutionLoggingEnabled\" | \"socTrendsEnabled\" | \"responseActionsEnabled\" | \"endpointResponseActionsEnabled\" | \"responseActionUploadEnabled\" | \"alertsPageChartsEnabled\" | \"alertTypeEnabled\" | \"alertsPageFiltersEnabled\" | \"assistantModelEvaluation\" | \"newUserDetailsFlyout\" | \"riskScoringPersistence\" | \"riskScoringRoutesEnabled\" | \"esqlRulesDisabled\" | \"protectionUpdatesEnabled\" | undefined" + "\"tGridEnabled\" | \"tGridEventRenderedViewEnabled\" | \"excludePoliciesInFilterEnabled\" | \"kubernetesEnabled\" | \"chartEmbeddablesEnabled\" | \"donutChartEmbeddablesEnabled\" | \"alertsPreviewChartEmbeddablesEnabled\" | \"previewTelemetryUrlEnabled\" | \"insightsRelatedAlertsByProcessAncestry\" | \"extendedRuleExecutionLoggingEnabled\" | \"socTrendsEnabled\" | \"responseActionsEnabled\" | \"endpointResponseActionsEnabled\" | \"responseActionUploadEnabled\" | \"alertsPageChartsEnabled\" | \"alertTypeEnabled\" | \"alertsPageFiltersEnabled\" | \"assistantModelEvaluation\" | \"newUserDetailsFlyout\" | \"riskScoringPersistence\" | \"riskScoringRoutesEnabled\" | \"esqlRulesDisabled\" | \"protectionUpdatesEnabled\" | \"disableTimelineSaveTour\" | undefined" ], "path": "x-pack/plugins/security_solution/public/common/links/types.ts", "deprecated": false, @@ -1746,6 +1746,22 @@ "path": "x-pack/plugins/security_solution/public/timelines/store/timeline/model.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "securitySolution", + "id": "def-public.TimelineModel.changed", + "type": "CompoundType", + "tags": [], + "label": "changed", + "description": [ + "used to mark the timeline as unsaved in the UI" + ], + "signature": [ + "boolean | undefined" + ], + "path": "x-pack/plugins/security_solution/public/timelines/store/timeline/model.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -1791,7 +1807,7 @@ "label": "experimentalFeatures", "description": [], "signature": [ - "{ readonly tGridEnabled: boolean; readonly tGridEventRenderedViewEnabled: boolean; readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly chartEmbeddablesEnabled: boolean; readonly donutChartEmbeddablesEnabled: boolean; readonly alertsPreviewChartEmbeddablesEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly insightsRelatedAlertsByProcessAncestry: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly socTrendsEnabled: boolean; readonly responseActionsEnabled: boolean; readonly endpointResponseActionsEnabled: boolean; readonly responseActionUploadEnabled: boolean; readonly alertsPageChartsEnabled: boolean; readonly alertTypeEnabled: boolean; readonly alertsPageFiltersEnabled: boolean; readonly assistantModelEvaluation: boolean; readonly newUserDetailsFlyout: boolean; readonly riskScoringPersistence: boolean; readonly riskScoringRoutesEnabled: boolean; readonly esqlRulesDisabled: boolean; readonly protectionUpdatesEnabled: boolean; }" + "{ readonly tGridEnabled: boolean; readonly tGridEventRenderedViewEnabled: boolean; readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly chartEmbeddablesEnabled: boolean; readonly donutChartEmbeddablesEnabled: boolean; readonly alertsPreviewChartEmbeddablesEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly insightsRelatedAlertsByProcessAncestry: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly socTrendsEnabled: boolean; readonly responseActionsEnabled: boolean; readonly endpointResponseActionsEnabled: boolean; readonly responseActionUploadEnabled: boolean; readonly alertsPageChartsEnabled: boolean; readonly alertTypeEnabled: boolean; readonly alertsPageFiltersEnabled: boolean; readonly assistantModelEvaluation: boolean; readonly newUserDetailsFlyout: boolean; readonly riskScoringPersistence: boolean; readonly riskScoringRoutesEnabled: boolean; readonly esqlRulesDisabled: boolean; readonly protectionUpdatesEnabled: boolean; readonly disableTimelineSaveTour: boolean; }" ], "path": "x-pack/plugins/security_solution/public/types.ts", "deprecated": false, @@ -2864,7 +2880,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 alertsPreviewChartEmbeddablesEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly insightsRelatedAlertsByProcessAncestry: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly socTrendsEnabled: boolean; readonly responseActionsEnabled: boolean; readonly endpointResponseActionsEnabled: boolean; readonly responseActionUploadEnabled: boolean; readonly alertsPageChartsEnabled: boolean; readonly alertTypeEnabled: boolean; readonly alertsPageFiltersEnabled: boolean; readonly assistantModelEvaluation: boolean; readonly newUserDetailsFlyout: boolean; readonly riskScoringPersistence: boolean; readonly riskScoringRoutesEnabled: boolean; readonly esqlRulesDisabled: boolean; readonly protectionUpdatesEnabled: boolean; }" + "{ readonly tGridEnabled: boolean; readonly tGridEventRenderedViewEnabled: boolean; readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly chartEmbeddablesEnabled: boolean; readonly donutChartEmbeddablesEnabled: boolean; readonly alertsPreviewChartEmbeddablesEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly insightsRelatedAlertsByProcessAncestry: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly socTrendsEnabled: boolean; readonly responseActionsEnabled: boolean; readonly endpointResponseActionsEnabled: boolean; readonly responseActionUploadEnabled: boolean; readonly alertsPageChartsEnabled: boolean; readonly alertTypeEnabled: boolean; readonly alertsPageFiltersEnabled: boolean; readonly assistantModelEvaluation: boolean; readonly newUserDetailsFlyout: boolean; readonly riskScoringPersistence: boolean; readonly riskScoringRoutesEnabled: boolean; readonly esqlRulesDisabled: boolean; readonly protectionUpdatesEnabled: boolean; readonly disableTimelineSaveTour: boolean; }" ], "path": "x-pack/plugins/security_solution/server/plugin_contract.ts", "deprecated": false, @@ -3010,7 +3026,7 @@ "label": "ExperimentalFeatures", "description": [], "signature": [ - "{ readonly tGridEnabled: boolean; readonly tGridEventRenderedViewEnabled: boolean; readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly chartEmbeddablesEnabled: boolean; readonly donutChartEmbeddablesEnabled: boolean; readonly alertsPreviewChartEmbeddablesEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly insightsRelatedAlertsByProcessAncestry: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly socTrendsEnabled: boolean; readonly responseActionsEnabled: boolean; readonly endpointResponseActionsEnabled: boolean; readonly responseActionUploadEnabled: boolean; readonly alertsPageChartsEnabled: boolean; readonly alertTypeEnabled: boolean; readonly alertsPageFiltersEnabled: boolean; readonly assistantModelEvaluation: boolean; readonly newUserDetailsFlyout: boolean; readonly riskScoringPersistence: boolean; readonly riskScoringRoutesEnabled: boolean; readonly esqlRulesDisabled: boolean; readonly protectionUpdatesEnabled: boolean; }" + "{ readonly tGridEnabled: boolean; readonly tGridEventRenderedViewEnabled: boolean; readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly chartEmbeddablesEnabled: boolean; readonly donutChartEmbeddablesEnabled: boolean; readonly alertsPreviewChartEmbeddablesEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly insightsRelatedAlertsByProcessAncestry: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly socTrendsEnabled: boolean; readonly responseActionsEnabled: boolean; readonly endpointResponseActionsEnabled: boolean; readonly responseActionUploadEnabled: boolean; readonly alertsPageChartsEnabled: boolean; readonly alertTypeEnabled: boolean; readonly alertsPageFiltersEnabled: boolean; readonly assistantModelEvaluation: boolean; readonly newUserDetailsFlyout: boolean; readonly riskScoringPersistence: boolean; readonly riskScoringRoutesEnabled: boolean; readonly esqlRulesDisabled: boolean; readonly protectionUpdatesEnabled: boolean; readonly disableTimelineSaveTour: boolean; }" ], "path": "x-pack/plugins/security_solution/common/experimental_features.ts", "deprecated": false, @@ -3059,7 +3075,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: boolean; readonly tGridEventRenderedViewEnabled: boolean; readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly chartEmbeddablesEnabled: boolean; readonly donutChartEmbeddablesEnabled: boolean; readonly alertsPreviewChartEmbeddablesEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly insightsRelatedAlertsByProcessAncestry: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly socTrendsEnabled: boolean; readonly responseActionsEnabled: boolean; readonly endpointResponseActionsEnabled: boolean; readonly responseActionUploadEnabled: boolean; readonly alertsPageChartsEnabled: boolean; readonly alertTypeEnabled: boolean; readonly alertsPageFiltersEnabled: boolean; readonly assistantModelEvaluation: boolean; readonly newUserDetailsFlyout: boolean; readonly riskScoringPersistence: boolean; readonly riskScoringRoutesEnabled: boolean; readonly esqlRulesDisabled: boolean; readonly protectionUpdatesEnabled: boolean; }" + "{ readonly tGridEnabled: boolean; readonly tGridEventRenderedViewEnabled: boolean; readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly chartEmbeddablesEnabled: boolean; readonly donutChartEmbeddablesEnabled: boolean; readonly alertsPreviewChartEmbeddablesEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly insightsRelatedAlertsByProcessAncestry: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly socTrendsEnabled: boolean; readonly responseActionsEnabled: boolean; readonly endpointResponseActionsEnabled: boolean; readonly responseActionUploadEnabled: boolean; readonly alertsPageChartsEnabled: boolean; readonly alertTypeEnabled: boolean; readonly alertsPageFiltersEnabled: boolean; readonly assistantModelEvaluation: boolean; readonly newUserDetailsFlyout: boolean; readonly riskScoringPersistence: boolean; readonly riskScoringRoutesEnabled: boolean; readonly esqlRulesDisabled: boolean; readonly protectionUpdatesEnabled: boolean; readonly disableTimelineSaveTour: boolean; }" ], "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 e092e4b26dd567..56233e694a44a6 100644 --- a/api_docs/security_solution.mdx +++ b/api_docs/security_solution.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/securitySolution title: "securitySolution" image: https://source.unsplash.com/400x175/?github description: API docs for the securitySolution plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'securitySolution'] --- import securitySolutionObj from './security_solution.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/security-solution](https://github.com/orgs/elastic/teams/secur | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 173 | 0 | 106 | 35 | +| 174 | 0 | 106 | 35 | ## Client diff --git a/api_docs/security_solution_ess.mdx b/api_docs/security_solution_ess.mdx index 3917e7063d0eae..d27a0e8a6a917e 100644 --- a/api_docs/security_solution_ess.mdx +++ b/api_docs/security_solution_ess.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/securitySolutionEss title: "securitySolutionEss" image: https://source.unsplash.com/400x175/?github description: API docs for the securitySolutionEss plugin -date: 2023-11-06 +date: 2023-11-10 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 f15f876a1da016..74f1db94af42b3 100644 --- a/api_docs/security_solution_serverless.mdx +++ b/api_docs/security_solution_serverless.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/securitySolutionServerless title: "securitySolutionServerless" image: https://source.unsplash.com/400x175/?github description: API docs for the securitySolutionServerless plugin -date: 2023-11-06 +date: 2023-11-10 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 7ad6e4a78fb9ff..3da87901fd7166 100644 --- a/api_docs/serverless.mdx +++ b/api_docs/serverless.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/serverless title: "serverless" image: https://source.unsplash.com/400x175/?github description: API docs for the serverless plugin -date: 2023-11-06 +date: 2023-11-10 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 427fd3e57996f4..77380a3af0bb0b 100644 --- a/api_docs/serverless_observability.mdx +++ b/api_docs/serverless_observability.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/serverlessObservability title: "serverlessObservability" image: https://source.unsplash.com/400x175/?github description: API docs for the serverlessObservability plugin -date: 2023-11-06 +date: 2023-11-10 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 1181dba00bc2c2..c31e05f44e48ac 100644 --- a/api_docs/serverless_search.mdx +++ b/api_docs/serverless_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/serverlessSearch title: "serverlessSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the serverlessSearch plugin -date: 2023-11-06 +date: 2023-11-10 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 7a7cb1b3d4dff3..124a0266a746dd 100644 --- a/api_docs/session_view.mdx +++ b/api_docs/session_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/sessionView title: "sessionView" image: https://source.unsplash.com/400x175/?github description: API docs for the sessionView plugin -date: 2023-11-06 +date: 2023-11-10 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 bfa99db78a88f0..e8f6e5bcc84b7b 100644 --- a/api_docs/share.mdx +++ b/api_docs/share.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/share title: "share" image: https://source.unsplash.com/400x175/?github description: API docs for the share plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'share'] --- import shareObj from './share.devdocs.json'; diff --git a/api_docs/snapshot_restore.mdx b/api_docs/snapshot_restore.mdx index 06830486014425..659e64e03c94cd 100644 --- a/api_docs/snapshot_restore.mdx +++ b/api_docs/snapshot_restore.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/snapshotRestore title: "snapshotRestore" image: https://source.unsplash.com/400x175/?github description: API docs for the snapshotRestore plugin -date: 2023-11-06 +date: 2023-11-10 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 72bd4f5b15213f..65f8b144fd5bdf 100644 --- a/api_docs/spaces.mdx +++ b/api_docs/spaces.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/spaces title: "spaces" image: https://source.unsplash.com/400x175/?github description: API docs for the spaces plugin -date: 2023-11-06 +date: 2023-11-10 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 e107b06f285b03..afb734134ec302 100644 --- a/api_docs/stack_alerts.mdx +++ b/api_docs/stack_alerts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/stackAlerts title: "stackAlerts" image: https://source.unsplash.com/400x175/?github description: API docs for the stackAlerts plugin -date: 2023-11-06 +date: 2023-11-10 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 7795802b49c690..72008768874e15 100644 --- a/api_docs/stack_connectors.mdx +++ b/api_docs/stack_connectors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/stackConnectors title: "stackConnectors" image: https://source.unsplash.com/400x175/?github description: API docs for the stackConnectors plugin -date: 2023-11-06 +date: 2023-11-10 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 10b7df753471f9..57e274d294d3c3 100644 --- a/api_docs/task_manager.mdx +++ b/api_docs/task_manager.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/taskManager title: "taskManager" image: https://source.unsplash.com/400x175/?github description: API docs for the taskManager plugin -date: 2023-11-06 +date: 2023-11-10 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 6360fa40bda4c8..80b7e4ae53110c 100644 --- a/api_docs/telemetry.mdx +++ b/api_docs/telemetry.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetry title: "telemetry" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetry plugin -date: 2023-11-06 +date: 2023-11-10 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 90af0932fdd920..1fcb29570c40bf 100644 --- a/api_docs/telemetry_collection_manager.mdx +++ b/api_docs/telemetry_collection_manager.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryCollectionManager title: "telemetryCollectionManager" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryCollectionManager plugin -date: 2023-11-06 +date: 2023-11-10 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 e6c9e24d27dddd..cd97d832161f5e 100644 --- a/api_docs/telemetry_collection_xpack.mdx +++ b/api_docs/telemetry_collection_xpack.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryCollectionXpack title: "telemetryCollectionXpack" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryCollectionXpack plugin -date: 2023-11-06 +date: 2023-11-10 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 cdb89a5e33e419..7a5a7677b71c44 100644 --- a/api_docs/telemetry_management_section.mdx +++ b/api_docs/telemetry_management_section.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryManagementSection title: "telemetryManagementSection" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryManagementSection plugin -date: 2023-11-06 +date: 2023-11-10 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 7f7ffe2d8c8e06..34df0015bac47f 100644 --- a/api_docs/text_based_languages.mdx +++ b/api_docs/text_based_languages.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/textBasedLanguages title: "textBasedLanguages" image: https://source.unsplash.com/400x175/?github description: API docs for the textBasedLanguages plugin -date: 2023-11-06 +date: 2023-11-10 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 7af15a78e537c4..98999fd7710b6f 100644 --- a/api_docs/threat_intelligence.mdx +++ b/api_docs/threat_intelligence.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/threatIntelligence title: "threatIntelligence" image: https://source.unsplash.com/400x175/?github description: API docs for the threatIntelligence plugin -date: 2023-11-06 +date: 2023-11-10 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 5c87d73da129f5..819609450dcd6e 100644 --- a/api_docs/timelines.mdx +++ b/api_docs/timelines.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/timelines title: "timelines" image: https://source.unsplash.com/400x175/?github description: API docs for the timelines plugin -date: 2023-11-06 +date: 2023-11-10 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 48c5e01ee81522..89c3394ff92283 100644 --- a/api_docs/transform.mdx +++ b/api_docs/transform.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/transform title: "transform" image: https://source.unsplash.com/400x175/?github description: API docs for the transform plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'transform'] --- import transformObj from './transform.devdocs.json'; diff --git a/api_docs/triggers_actions_ui.mdx b/api_docs/triggers_actions_ui.mdx index aa54d5fac7346f..8c73bb0d5aa73a 100644 --- a/api_docs/triggers_actions_ui.mdx +++ b/api_docs/triggers_actions_ui.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/triggersActionsUi title: "triggersActionsUi" image: https://source.unsplash.com/400x175/?github description: API docs for the triggersActionsUi plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'triggersActionsUi'] --- import triggersActionsUiObj from './triggers_actions_ui.devdocs.json'; diff --git a/api_docs/ui_actions.mdx b/api_docs/ui_actions.mdx index 9bc5d69c12d8f8..09ea8571e5e37f 100644 --- a/api_docs/ui_actions.mdx +++ b/api_docs/ui_actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/uiActions title: "uiActions" image: https://source.unsplash.com/400x175/?github description: API docs for the uiActions plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uiActions'] --- import uiActionsObj from './ui_actions.devdocs.json'; diff --git a/api_docs/ui_actions_enhanced.mdx b/api_docs/ui_actions_enhanced.mdx index decb968f91f9dc..ab66378e46af63 100644 --- a/api_docs/ui_actions_enhanced.mdx +++ b/api_docs/ui_actions_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/uiActionsEnhanced title: "uiActionsEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the uiActionsEnhanced plugin -date: 2023-11-06 +date: 2023-11-10 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 654ec8def10f68..97b7dd9ecef177 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: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedDocViewer'] --- import unifiedDocViewerObj from './unified_doc_viewer.devdocs.json'; diff --git a/api_docs/unified_histogram.mdx b/api_docs/unified_histogram.mdx index 8b8204e5168272..e387084a28c5a3 100644 --- a/api_docs/unified_histogram.mdx +++ b/api_docs/unified_histogram.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedHistogram title: "unifiedHistogram" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedHistogram plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedHistogram'] --- import unifiedHistogramObj from './unified_histogram.devdocs.json'; diff --git a/api_docs/unified_search.mdx b/api_docs/unified_search.mdx index cdcba13ec38a35..a5485bf130a5d5 100644 --- a/api_docs/unified_search.mdx +++ b/api_docs/unified_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedSearch title: "unifiedSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedSearch plugin -date: 2023-11-06 +date: 2023-11-10 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 a05608dd2a4855..ce1b8e55bbcab7 100644 --- a/api_docs/unified_search_autocomplete.mdx +++ b/api_docs/unified_search_autocomplete.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedSearch-autocomplete title: "unifiedSearch.autocomplete" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedSearch.autocomplete plugin -date: 2023-11-06 +date: 2023-11-10 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 60925d4fd2a1b6..a2218269f4ce5f 100644 --- a/api_docs/uptime.mdx +++ b/api_docs/uptime.mdx @@ -8,14 +8,14 @@ slug: /kibana-dev-docs/api/uptime title: "uptime" image: https://source.unsplash.com/400x175/?github description: API docs for the uptime plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uptime'] --- import uptimeObj from './uptime.devdocs.json'; This plugin visualizes data from Heartbeat, and integrates with other Observability solutions. -Contact [@elastic/uptime](https://github.com/orgs/elastic/teams/uptime) for questions regarding this plugin. +Contact [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) for questions regarding this plugin. **Code health stats** diff --git a/api_docs/url_forwarding.mdx b/api_docs/url_forwarding.mdx index f320931291ebbb..b1a9193a7fae20 100644 --- a/api_docs/url_forwarding.mdx +++ b/api_docs/url_forwarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/urlForwarding title: "urlForwarding" image: https://source.unsplash.com/400x175/?github description: API docs for the urlForwarding plugin -date: 2023-11-06 +date: 2023-11-10 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 489a290076069f..17cb54fde2442e 100644 --- a/api_docs/usage_collection.mdx +++ b/api_docs/usage_collection.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/usageCollection title: "usageCollection" image: https://source.unsplash.com/400x175/?github description: API docs for the usageCollection plugin -date: 2023-11-06 +date: 2023-11-10 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 bb1b196f6e2ea2..adc6a59bc27d49 100644 --- a/api_docs/ux.mdx +++ b/api_docs/ux.mdx @@ -8,14 +8,14 @@ slug: /kibana-dev-docs/api/ux title: "ux" image: https://source.unsplash.com/400x175/?github description: API docs for the ux plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ux'] --- import uxObj from './ux.devdocs.json'; -Contact [@elastic/uptime](https://github.com/orgs/elastic/teams/uptime) for questions regarding this plugin. +Contact [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) for questions regarding this plugin. **Code health stats** diff --git a/api_docs/vis_default_editor.mdx b/api_docs/vis_default_editor.mdx index 9fe29bcdb0140e..5d83a164601ebd 100644 --- a/api_docs/vis_default_editor.mdx +++ b/api_docs/vis_default_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visDefaultEditor title: "visDefaultEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the visDefaultEditor plugin -date: 2023-11-06 +date: 2023-11-10 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 77843db79eeb24..0c10deb217431c 100644 --- a/api_docs/vis_type_gauge.mdx +++ b/api_docs/vis_type_gauge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeGauge title: "visTypeGauge" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeGauge plugin -date: 2023-11-06 +date: 2023-11-10 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 51f561903e745b..c532426fb79165 100644 --- a/api_docs/vis_type_heatmap.mdx +++ b/api_docs/vis_type_heatmap.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeHeatmap title: "visTypeHeatmap" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeHeatmap plugin -date: 2023-11-06 +date: 2023-11-10 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 ccb72d01e5b4a7..a8635835c673af 100644 --- a/api_docs/vis_type_pie.mdx +++ b/api_docs/vis_type_pie.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypePie title: "visTypePie" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypePie plugin -date: 2023-11-06 +date: 2023-11-10 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 c7cc13451a04fe..65cc4d4adacb96 100644 --- a/api_docs/vis_type_table.mdx +++ b/api_docs/vis_type_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTable title: "visTypeTable" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTable plugin -date: 2023-11-06 +date: 2023-11-10 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 73b60f34e2054b..4855114b873fe7 100644 --- a/api_docs/vis_type_timelion.mdx +++ b/api_docs/vis_type_timelion.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTimelion title: "visTypeTimelion" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTimelion plugin -date: 2023-11-06 +date: 2023-11-10 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 ba42874007a623..d32b566d45383e 100644 --- a/api_docs/vis_type_timeseries.mdx +++ b/api_docs/vis_type_timeseries.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTimeseries title: "visTypeTimeseries" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTimeseries plugin -date: 2023-11-06 +date: 2023-11-10 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 fd42efdf6ae12d..6ccc3abe170cf9 100644 --- a/api_docs/vis_type_vega.mdx +++ b/api_docs/vis_type_vega.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeVega title: "visTypeVega" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeVega plugin -date: 2023-11-06 +date: 2023-11-10 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 703704eea7fcba..00f2ea78e7d663 100644 --- a/api_docs/vis_type_vislib.mdx +++ b/api_docs/vis_type_vislib.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeVislib title: "visTypeVislib" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeVislib plugin -date: 2023-11-06 +date: 2023-11-10 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 fc9716bcefacbe..7df847499812f7 100644 --- a/api_docs/vis_type_xy.mdx +++ b/api_docs/vis_type_xy.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeXy title: "visTypeXy" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeXy plugin -date: 2023-11-06 +date: 2023-11-10 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeXy'] --- import visTypeXyObj from './vis_type_xy.devdocs.json'; diff --git a/api_docs/visualizations.mdx b/api_docs/visualizations.mdx index 8a355f08748601..3d9c01a701c3ae 100644 --- a/api_docs/visualizations.mdx +++ b/api_docs/visualizations.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visualizations title: "visualizations" image: https://source.unsplash.com/400x175/?github description: API docs for the visualizations plugin -date: 2023-11-06 +date: 2023-11-10 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 885bdcb9962b10..68f340fb64c054 100644 --- a/config/serverless.oblt.yml +++ b/config/serverless.oblt.yml @@ -47,14 +47,19 @@ xpack.fleet.internal.registry.excludePackages: [ 'endpoint', 'beaconing', 'osquery_manager', + # synthetics is not enabled yet 'synthetics', 'synthetics_dashboards', + # Removed in 8.11 integrations 'cisco', 'microsoft', 'symantec', 'cyberark', + + # ML integrations + 'dga', ] ## Required for force installation of APM Package diff --git a/config/serverless.security.yml b/config/serverless.security.yml index 0e59a104317c5d..7f749afc7626d3 100644 --- a/config/serverless.security.yml +++ b/config/serverless.security.yml @@ -48,11 +48,15 @@ xpack.fleet.internal.registry.excludePackages: [ 'apm', 'synthetics', 'synthetics_dashboards', + # Removed in 8.11 integrations 'cisco', 'microsoft', 'symantec', 'cyberark', + + # ML integrations + 'dga', ] # fleet_server package installed to publish agent metrics xpack.fleet.packages: diff --git a/config/serverless.yml b/config/serverless.yml index e830bb33e6fa91..a6aba80736f2da 100644 --- a/config/serverless.yml +++ b/config/serverless.yml @@ -146,6 +146,7 @@ xpack.task_manager.requeue_invalid_tasks.enabled: true # Reporting feature xpack.screenshotting.enabled: false +xpack.reporting.queue.pollInterval: 3m xpack.reporting.roles.enabled: false xpack.reporting.statefulSettings.enabled: false diff --git a/dev_docs/tutorials/ci.mdx b/dev_docs/tutorials/ci.mdx index a332d7e616e6c2..2234143c32664b 100644 --- a/dev_docs/tutorials/ci.mdx +++ b/dev_docs/tutorials/ci.mdx @@ -3,7 +3,7 @@ id: kibDevTutorialCI slug: /kibana-dev-docs/tutorials/ci title: CI description: CI -date: 2022-02-03 +date: 2023-11-08 tags: ['kibana', 'onboarding', 'dev', 'ci'] --- @@ -29,28 +29,76 @@ Build documentation from the root `docs` folder. ### Labels -Labels can be added to a pull request to run conditional pipelines. +Labels can be added to a pull request to run conditional pipelines. Build artifacts will be available on the "Artifacts" tab of the "Build Kibana Distribution and Plugins" step. + +#### `ci:all-cypress-suites` + +Some Cypress test suites are only run when code changes are made in certain files, typically files with overlapping test coverage. Adding this label will cause all Cypress tests to run. + +#### `ci:build-all-platforms` + +Build Windows, macOS, and Linux archives. + +#### `ci:build-canvas-shareable-runtime` + +Build the Canvas shareable runtime and include it in the distribution. + +#### `ci:build-cdn-assets` + +Build an archive that can be used to serve Kibana's static assets. + +#### `ci:build-cloud-image` + +Build cloud Docker images that can be used for testing deployments on Elastic Cloud. + +#### `ci:build-os-packages` + +Build Docker images, and Debian and RPM packages. + +#### `ci:build-serverless-image` + +Build serverless Docker images that can be used for testing deployments on Elastic Cloud. + +#### `ci:build-storybooks` + +Build and upload storybooks. + +#### `ci:build-webpack-bundle-analyzer` + +Build and upload a bundle report generated by `webpack-bundle-analyzer`. #### `ci:cloud-deploy` -Create or update a deployment on Elastic Cloud. +Create or update a deployment on Elastic Cloud production. + +#### `ci:cloud-persist-deployment` + +Prevents an existing deployment from being shutdown due to inactivity. #### `ci:cloud-redeploy` Create a new deployment on Elastic Cloud. Previous deployments linked to a pull request will be shutdown and data will not be preserved. -#### `ci:build-all-platforms` +#### `ci:collect-apm` -Build Windows, macOS, and Linux archives. Artifacts will be available on the "Artifacts" tab of the "Build Kibana Distribution and Plugins" step. +Collect APM metrics, available for viewing on the Kibana CI APM cluster. -#### `ci:build-os-packages` +#### `ci:no-auto-commit` -Build Docker images, and Debian and RPM packages. Artifacts will be available on the "Artifacts" tab of the "Build Kibana Distribution and Plugins" step. +Skip auto-committing changed files. -#### `ci:build-cloud-image` +#### `ci:project-deploy-elasticsearch` -Build Docker images that can be used for testing deployments on Elastic Cloud in the CFT region (gcp-us-west2). Artifacts will be available on the "Artifacts" tab of the "Build Kibana Distribution and Plugins" step. +Create or update a serverless Elasticsearch project on Elastic Cloud QA. -#### `ci:all-cypress-suites` +#### `ci:project-deploy-observability` + +Create or update a serverless Observability project on Elastic Cloud QA. + +#### `ci:project-deploy-security` + +Create or update a serverless Security project on Elastic Cloud QA. + +#### `ci:project-persist-deployment` -By default, Cypress test suites are only run when code changes are made in certain files, typically files with overlapping test coverage. Adding this label will cause all Cypress tests to run. +Prevents an existing deployment from being shutdown due to inactivity. diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index 868ef59f479666..df3f4c8ec855df 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -519,6 +519,10 @@ Plugin server-side only. Plugin has three main functions: |Adds drilldown capabilities to dashboard. Owned by the Kibana App team. +|{kib-repo}blob/{branch}/x-pack/plugins/dataset_quality/README.md[datasetQuality] +|In order to make ongoing maintenance of log collection easy we want to introduce the concept of dataset quality, where users can easily get an overview on the datasets they have with information such as integration, size, last activity, among others. + + |{kib-repo}blob/{branch}/x-pack/plugins/data_visualizer/README.md[dataVisualizer] |The data_visualizer plugin enables you to explore the fields in your data. diff --git a/examples/error_boundary/README.md b/examples/error_boundary/README.md new file mode 100755 index 00000000000000..3a293c9a7b4651 --- /dev/null +++ b/examples/error_boundary/README.md @@ -0,0 +1,3 @@ +## Error Boundary Example + +A very simple example plugin for testing Kibana Error Boundary. diff --git a/examples/error_boundary/kibana.jsonc b/examples/error_boundary/kibana.jsonc new file mode 100644 index 00000000000000..3acabfbb5006cf --- /dev/null +++ b/examples/error_boundary/kibana.jsonc @@ -0,0 +1,14 @@ +{ + "type": "plugin", + "id": "@kbn/error-boundary-example-plugin", + "owner": "@elastic/appex-sharedux", + "description": "A plugin which exemplifes the KibanaErrorBoundary", + "plugin": { + "id": "error_boundary_example", + "server": false, + "browser": true, + "requiredPlugins": [ + "developerExamples" + ] + } +} diff --git a/examples/error_boundary/public/index.ts b/examples/error_boundary/public/index.ts new file mode 100755 index 00000000000000..41d35a8f5bec12 --- /dev/null +++ b/examples/error_boundary/public/index.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import { ErrorBoundaryExamplePlugin } from './plugin'; + +export function plugin() { + return new ErrorBoundaryExamplePlugin(); +} diff --git a/examples/error_boundary/public/plugin.tsx b/examples/error_boundary/public/plugin.tsx new file mode 100755 index 00000000000000..2c5d4e74870054 --- /dev/null +++ b/examples/error_boundary/public/plugin.tsx @@ -0,0 +1,111 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import { EuiButton } from '@elastic/eui'; + +import React, { useState } from 'react'; +import ReactDOM from 'react-dom'; + +import { AppMountParameters, CoreSetup, CoreStart, Plugin } from '@kbn/core/public'; +import { DeveloperExamplesSetup } from '@kbn/developer-examples-plugin/public'; +import { KibanaErrorBoundary, KibanaErrorBoundaryProvider } from '@kbn/shared-ux-error-boundary'; +import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template'; + +interface SetupDeps { + developerExamples: DeveloperExamplesSetup; +} + +const useErrors = () => { + return useState(false); +}; + +export const FatalComponent = () => { + const [hasError, setHasError] = useErrors(); + + if (hasError) { + const fatalError = new Error('Example of unknown error type'); + throw fatalError; + } + + return ( + { + setHasError(true); + }} + data-test-subj="fatalErrorBtn" + > + Click for fatal error + + ); +}; + +export const RecoverableComponent = () => { + const [hasError, setHasError] = useErrors(); + + if (hasError) { + // FIXME: use network interception to disable responses + // for chunk requests and attempt to lazy-load a component + // https://github.com/elastic/kibana/issues/170777 + const upgradeError = new Error('ChunkLoadError'); + upgradeError.name = 'ChunkLoadError'; + throw upgradeError; + } + + return ( + { + setHasError(true); + }} + data-test-subj="recoverableErrorBtn" + > + Click for recoverable error + + ); +}; + +export class ErrorBoundaryExamplePlugin implements Plugin { + public setup(core: CoreSetup, deps: SetupDeps) { + // Register an application into the side navigation menu + core.application.register({ + id: 'errorBoundaryExample', + title: 'Error Boundary Example', + async mount({ element }: AppMountParameters) { + ReactDOM.render( + + + + + + + + + + + + + , + element + ); + return () => ReactDOM.unmountComponentAtNode(element); + }, + }); + + // This section is only needed to get this example plugin to show up in our Developer Examples. + deps.developerExamples.register({ + appId: 'errorBoundaryExample', + title: 'Error Boundary Example Application', + description: `Build a plugin that registers an application that simply says "Error Boundary Example"`, + }); + } + public start(_core: CoreStart) { + return {}; + } + public stop() {} +} diff --git a/examples/error_boundary/tsconfig.json b/examples/error_boundary/tsconfig.json new file mode 100644 index 00000000000000..2df5023cbdadb5 --- /dev/null +++ b/examples/error_boundary/tsconfig.json @@ -0,0 +1,22 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types" + }, + "include": [ + "index.ts", + "common/**/*.ts", + "public/**/*.ts", + "public/**/*.tsx", + "../../typings/**/*" + ], + "exclude": [ + "target/**/*" + ], + "kbn_references": [ + "@kbn/core", + "@kbn/developer-examples-plugin", + "@kbn/shared-ux-error-boundary", + "@kbn/shared-ux-page-kibana-template" + ] +} diff --git a/nav-kibana-dev.docnav.json b/nav-kibana-dev.docnav.json index a02acdd367fa45..c85c804cdfda4e 100644 --- a/nav-kibana-dev.docnav.json +++ b/nav-kibana-dev.docnav.json @@ -193,6 +193,17 @@ } ] }, + { + "label": "Serverless", + "items": [ + { + "id": "ktServerlessReleaseOverview" + }, + { + "id": "ktCustomServerlessImage" + } + ] + }, { "label": "Contributors Newsletters", "items": [ diff --git a/package.json b/package.json index 3f3a05f840cb06..7db00b814d99d2 100644 --- a/package.json +++ b/package.json @@ -101,7 +101,7 @@ "@elastic/charts": "60.0.0", "@elastic/datemath": "5.0.3", "@elastic/elasticsearch": "npm:@elastic/elasticsearch-canary@8.9.1-canary.1", - "@elastic/ems-client": "8.5.0", + "@elastic/ems-client": "8.5.1", "@elastic/eui": "90.0.0", "@elastic/filesaver": "1.1.2", "@elastic/node-crypto": "1.2.1", @@ -364,6 +364,7 @@ "@kbn/data-view-management-plugin": "link:src/plugins/data_view_management", "@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", "@kbn/datemath": "link:packages/kbn-datemath", "@kbn/deeplinks-analytics": "link:packages/deeplinks/analytics", "@kbn/deeplinks-devtools": "link:packages/deeplinks/devtools", @@ -398,6 +399,7 @@ "@kbn/embedded-lens-example-plugin": "link:x-pack/examples/embedded_lens_example", "@kbn/encrypted-saved-objects-plugin": "link:x-pack/plugins/encrypted_saved_objects", "@kbn/enterprise-search-plugin": "link:x-pack/plugins/enterprise_search", + "@kbn/error-boundary-example-plugin": "link:examples/error_boundary", "@kbn/es-errors": "link:packages/kbn-es-errors", "@kbn/es-query": "link:packages/kbn-es-query", "@kbn/es-types": "link:packages/kbn-es-types", @@ -937,7 +939,7 @@ "json-stringify-safe": "5.0.1", "jsonwebtoken": "^9.0.0", "jsts": "^1.6.2", - "kea": "^2.4.2", + "kea": "^2.6.0", "langchain": "^0.0.151", "launchdarkly-js-client-sdk": "^3.1.4", "launchdarkly-node-server-sdk": "^7.0.3", @@ -1276,7 +1278,6 @@ "@storybook/react-docgen-typescript-plugin": "^1.0.1", "@storybook/testing-react": "^1.3.0", "@storybook/theming": "^6.5.16", - "@testing-library/dom": "^8.19.0", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^12.1.5", "@testing-library/react-hooks": "^8.0.1", diff --git a/packages/core/capabilities/core-capabilities-server-internal/src/capabilities_service.test.ts b/packages/core/capabilities/core-capabilities-server-internal/src/capabilities_service.test.ts index 29e6c5a937898f..23e2e06c02c1f9 100644 --- a/packages/core/capabilities/core-capabilities-server-internal/src/capabilities_service.test.ts +++ b/packages/core/capabilities/core-capabilities-server-internal/src/capabilities_service.test.ts @@ -8,6 +8,7 @@ import { mockCoreContext } from '@kbn/core-base-server-mocks'; import { mockRouter, RouterMock } from '@kbn/core-http-router-server-mocks'; +import type { KibanaRequest } from '@kbn/core-http-server'; import { httpServiceMock, InternalHttpServicePrebootMock, @@ -16,6 +17,8 @@ import { import type { CapabilitiesSetup } from '@kbn/core-capabilities-server'; import { CapabilitiesService } from './capabilities_service'; +const fakeRequest = {} as KibanaRequest; + describe('CapabilitiesService', () => { let http: InternalHttpServiceSetupMock; let service: CapabilitiesService; @@ -71,7 +74,8 @@ describe('CapabilitiesService', () => { catalogue: { myPlugin: true }, })); const start = service.start(); - expect(await start.resolveCapabilities({} as any)).toMatchInlineSnapshot(` + expect(await start.resolveCapabilities(fakeRequest, { capabilityPath: '*' })) + .toMatchInlineSnapshot(` Object { "catalogue": Object { "myPlugin": true, @@ -100,7 +104,8 @@ describe('CapabilitiesService', () => { }, })); const start = service.start(); - expect(await start.resolveCapabilities({} as any)).toMatchInlineSnapshot(` + expect(await start.resolveCapabilities(fakeRequest, { capabilityPath: '*' })) + .toMatchInlineSnapshot(` Object { "catalogue": Object { "A": true, @@ -123,17 +128,21 @@ describe('CapabilitiesService', () => { setup.registerProvider(() => ({ catalogue: { a: true, b: true, c: true }, })); - setup.registerSwitcher((req, capabilities) => { - return { - ...capabilities, - catalogue: { - ...capabilities.catalogue, - b: false, - }, - }; - }); + setup.registerSwitcher( + (req, capabilities) => { + return { + ...capabilities, + catalogue: { + ...capabilities.catalogue, + b: false, + }, + }; + }, + { capabilityPath: '*' } + ); const start = service.start(); - expect(await start.resolveCapabilities({} as any)).toMatchInlineSnapshot(` + expect(await start.resolveCapabilities(fakeRequest, { capabilityPath: '*' })) + .toMatchInlineSnapshot(` Object { "catalogue": Object { "a": true, @@ -162,29 +171,39 @@ describe('CapabilitiesService', () => { c: true, }, })); - setup.registerSwitcher((req, capabilities) => { - return { - catalogue: { - b: false, - }, - }; - }); - - setup.registerSwitcher((req, capabilities) => { - return { - navLinks: { c: false }, - }; - }); - setup.registerSwitcher((req, capabilities) => { - return { - customSection: { - c: false, - }, - }; - }); + setup.registerSwitcher( + (req, capabilities) => { + return { + catalogue: { + b: false, + }, + }; + }, + { capabilityPath: '*' } + ); + + setup.registerSwitcher( + (req, capabilities) => { + return { + navLinks: { c: false }, + }; + }, + { capabilityPath: '*' } + ); + setup.registerSwitcher( + (req, capabilities) => { + return { + customSection: { + c: false, + }, + }; + }, + { capabilityPath: '*' } + ); const start = service.start(); - expect(await start.resolveCapabilities({} as any)).toMatchInlineSnapshot(` + expect(await start.resolveCapabilities(fakeRequest, { capabilityPath: '*' })) + .toMatchInlineSnapshot(` Object { "catalogue": Object { "a": true, @@ -206,12 +225,15 @@ describe('CapabilitiesService', () => { it('allows to indicate that default capabilities should be returned', async () => { setup.registerProvider(() => ({ customSection: { isDefault: true } })); - setup.registerSwitcher((req, capabilities, useDefaultCapabilities) => - useDefaultCapabilities ? capabilities : { customSection: { isDefault: false } } + setup.registerSwitcher( + (req, capabilities, useDefaultCapabilities) => + useDefaultCapabilities ? capabilities : { customSection: { isDefault: false } }, + { capabilityPath: '*' } ); const start = service.start(); - expect(await start.resolveCapabilities({} as any)).toMatchInlineSnapshot(` + expect(await start.resolveCapabilities(fakeRequest, { capabilityPath: '*' })) + .toMatchInlineSnapshot(` Object { "catalogue": Object {}, "customSection": Object { @@ -221,8 +243,12 @@ describe('CapabilitiesService', () => { "navLinks": Object {}, } `); - expect(await start.resolveCapabilities({} as any, { useDefaultCapabilities: false })) - .toMatchInlineSnapshot(` + expect( + await start.resolveCapabilities({} as any, { + useDefaultCapabilities: false, + capabilityPath: '*', + }) + ).toMatchInlineSnapshot(` Object { "catalogue": Object {}, "customSection": Object { @@ -232,8 +258,12 @@ describe('CapabilitiesService', () => { "navLinks": Object {}, } `); - expect(await start.resolveCapabilities({} as any, { useDefaultCapabilities: true })) - .toMatchInlineSnapshot(` + expect( + await start.resolveCapabilities({} as any, { + useDefaultCapabilities: true, + capabilityPath: '*', + }) + ).toMatchInlineSnapshot(` Object { "catalogue": Object {}, "customSection": Object { diff --git a/packages/core/capabilities/core-capabilities-server-internal/src/capabilities_service.ts b/packages/core/capabilities/core-capabilities-server-internal/src/capabilities_service.ts index 4abb13eb03b827..bdcaad897f9aa7 100644 --- a/packages/core/capabilities/core-capabilities-server-internal/src/capabilities_service.ts +++ b/packages/core/capabilities/core-capabilities-server-internal/src/capabilities_service.ts @@ -18,7 +18,9 @@ import type { CapabilitiesSwitcher, CapabilitiesStart, CapabilitiesSetup, + CapabilitiesSwitcherOptions, } from '@kbn/core-capabilities-server'; +import type { SwitcherWithOptions } from './types'; import { mergeCapabilities } from './merge_capabilities'; import { getCapabilitiesResolver, CapabilitiesResolver } from './resolve_capabilities'; import { registerRoutes } from './routes'; @@ -41,8 +43,9 @@ const defaultCapabilities: Capabilities = { export class CapabilitiesService { private readonly logger: Logger; private readonly capabilitiesProviders: CapabilitiesProvider[] = []; - private readonly capabilitiesSwitchers: CapabilitiesSwitcher[] = []; + private readonly capabilitiesSwitchers: SwitcherWithOptions[] = []; private readonly resolveCapabilities: CapabilitiesResolver; + private started = false; constructor(core: CoreContext) { this.logger = core.logger.get('capabilities-service'); @@ -73,18 +76,38 @@ export class CapabilitiesService { return { registerProvider: (provider: CapabilitiesProvider) => { + if (this.started) { + throw new Error('registerProvider cannot be called after #start'); + } this.capabilitiesProviders.push(provider); }, - registerSwitcher: (switcher: CapabilitiesSwitcher) => { - this.capabilitiesSwitchers.push(switcher); + registerSwitcher: (switcher: CapabilitiesSwitcher, options: CapabilitiesSwitcherOptions) => { + if (this.started) { + throw new Error('registerSwitcher cannot be called after #start'); + } + this.capabilitiesSwitchers.push({ + switcher, + capabilityPath: Array.isArray(options.capabilityPath) + ? options.capabilityPath + : [options.capabilityPath], + }); }, }; } public start(): CapabilitiesStart { + this.started = true; + return { resolveCapabilities: (request, options) => - this.resolveCapabilities(request, [], options?.useDefaultCapabilities ?? false), + this.resolveCapabilities({ + request, + capabilityPath: Array.isArray(options.capabilityPath) + ? options.capabilityPath + : [options.capabilityPath], + useDefaultCapabilities: options.useDefaultCapabilities ?? false, + applications: [], + }), }; } } diff --git a/packages/core/capabilities/core-capabilities-server-internal/src/resolve_capabilities.test.mocks.ts b/packages/core/capabilities/core-capabilities-server-internal/src/resolve_capabilities.test.mocks.ts new file mode 100644 index 00000000000000..9724732917b762 --- /dev/null +++ b/packages/core/capabilities/core-capabilities-server-internal/src/resolve_capabilities.test.mocks.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. + */ + +const actualHelpers = jest.requireActual('./resolve_helpers'); + +export const splitIntoBucketsMock = jest.fn().mockImplementation(actualHelpers.splitIntoBuckets); + +jest.doMock('./resolve_helpers', () => { + return { + ...actualHelpers, + splitIntoBuckets: splitIntoBucketsMock, + }; +}); diff --git a/packages/core/capabilities/core-capabilities-server-internal/src/resolve_capabilities.test.ts b/packages/core/capabilities/core-capabilities-server-internal/src/resolve_capabilities.test.ts index 12589890f32e5e..7bd1e0e6d3832d 100644 --- a/packages/core/capabilities/core-capabilities-server-internal/src/resolve_capabilities.test.ts +++ b/packages/core/capabilities/core-capabilities-server-internal/src/resolve_capabilities.test.ts @@ -6,16 +6,20 @@ * Side Public License, v 1. */ +import { splitIntoBucketsMock } from './resolve_capabilities.test.mocks'; import type { KibanaRequest } from '@kbn/core-http-server'; import { httpServerMock } from '@kbn/core-http-server-mocks'; import type { Capabilities } from '@kbn/core-capabilities-common'; -import { resolveCapabilities } from './resolve_capabilities'; +import { getCapabilitiesResolver } from './resolve_capabilities'; +import type { SwitcherWithOptions } from './types'; describe('resolveCapabilities', () => { let defaultCaps: Capabilities; let request: KibanaRequest; beforeEach(() => { + splitIntoBucketsMock.mockClear(); + defaultCaps = { navLinks: {}, catalogue: {}, @@ -24,28 +28,53 @@ describe('resolveCapabilities', () => { request = httpServerMock.createKibanaRequest(); }); - it('returns the initial capabilities if no switcher are used', async () => { - const result = await resolveCapabilities(defaultCaps, [], request, [], true); - expect(result).toEqual(defaultCaps); - }); + describe('base feature', () => { + it('returns the initial capabilities if no switcher are used', async () => { + const result = await getCapabilitiesResolver( + () => defaultCaps, + () => [] + )({ + request, + capabilityPath: ['*'], + applications: [], + useDefaultCapabilities: false, + }); - it('applies the switcher to the capabilities ', async () => { - const caps = { - ...defaultCaps, - catalogue: { - A: true, - B: true, - }, - }; - const switcher = (req: KibanaRequest, capabilities: Capabilities) => ({ - ...capabilities, - catalogue: { - ...capabilities.catalogue, - A: false, - }, + expect(result).toEqual(defaultCaps); }); - const result = await resolveCapabilities(caps, [switcher], request, [], true); - expect(result).toMatchInlineSnapshot(` + + it('applies the switcher to the capabilities ', async () => { + const caps = { + ...defaultCaps, + catalogue: { + A: true, + B: true, + }, + }; + const switcher = (req: KibanaRequest, capabilities: Capabilities) => ({ + ...capabilities, + catalogue: { + ...capabilities.catalogue, + A: false, + }, + }); + + const result = await getCapabilitiesResolver( + () => caps, + () => [ + { + switcher, + capabilityPath: ['*'], + }, + ] + )({ + request, + capabilityPath: ['*'], + applications: [], + useDefaultCapabilities: false, + }); + + expect(result).toMatchInlineSnapshot(` Object { "catalogue": Object { "A": false, @@ -55,99 +84,423 @@ describe('resolveCapabilities', () => { "navLinks": Object {}, } `); - }); + }); + + it('does not mutate the input capabilities', async () => { + const caps = { + ...defaultCaps, + catalogue: { + A: true, + B: true, + }, + }; + const switcher = (req: KibanaRequest, capabilities: Capabilities) => ({ + ...capabilities, + catalogue: { + ...capabilities.catalogue, + A: false, + }, + }); + + await getCapabilitiesResolver( + () => caps, + () => [ + { + switcher, + capabilityPath: ['*'], + }, + ] + )({ + request, + capabilityPath: ['*'], + applications: [], + useDefaultCapabilities: false, + }); - it('does not mutate the input capabilities', async () => { - const caps = { - ...defaultCaps, - catalogue: { + expect(caps.catalogue).toEqual({ A: true, B: true, - }, - }; - const switcher = (req: KibanaRequest, capabilities: Capabilities) => ({ - ...capabilities, - catalogue: { - ...capabilities.catalogue, - A: false, - }, - }); - await resolveCapabilities(caps, [switcher], request, [], true); - expect(caps.catalogue).toEqual({ - A: true, - B: true, + }); }); - }); - it('ignores any added capability from the switcher', async () => { - const caps = { - ...defaultCaps, - catalogue: { + it('ignores any added capability from the switcher', async () => { + const caps = { + ...defaultCaps, + catalogue: { + A: true, + B: true, + }, + }; + const switcher = (req: KibanaRequest, capabilities: Capabilities) => ({ + ...capabilities, + catalogue: { + ...capabilities.catalogue, + C: false, + }, + }); + + const result = await getCapabilitiesResolver( + () => caps, + () => [ + { + switcher, + capabilityPath: ['*'], + }, + ] + )({ + request, + capabilityPath: ['*'], + applications: [], + useDefaultCapabilities: false, + }); + + expect(result.catalogue).toEqual({ A: true, B: true, - }, - }; - const switcher = (req: KibanaRequest, capabilities: Capabilities) => ({ - ...capabilities, - catalogue: { - ...capabilities.catalogue, - C: false, - }, + }); }); - const result = await resolveCapabilities(caps, [switcher], request, [], true); - expect(result.catalogue).toEqual({ - A: true, - B: true, - }); - }); - it('ignores any removed capability from the switcher', async () => { - const caps = { - ...defaultCaps, - catalogue: { + it('ignores any removed capability from the switcher', async () => { + const caps = { + ...defaultCaps, + catalogue: { + A: true, + B: true, + C: true, + }, + }; + const switcher = (req: KibanaRequest, capabilities: Capabilities) => ({ + ...capabilities, + catalogue: Object.entries(capabilities.catalogue) + .filter(([key]) => key !== 'B') + .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}), + }); + const result = await getCapabilitiesResolver( + () => caps, + () => [ + { + switcher, + capabilityPath: ['*'], + }, + ] + )({ + request, + capabilityPath: ['*'], + applications: [], + useDefaultCapabilities: false, + }); + expect(result.catalogue).toEqual({ A: true, B: true, C: true, - }, - }; - const switcher = (req: KibanaRequest, capabilities: Capabilities) => ({ - ...capabilities, - catalogue: Object.entries(capabilities.catalogue) - .filter(([key]) => key !== 'B') - .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}), - }); - const result = await resolveCapabilities(caps, [switcher], request, [], true); - expect(result.catalogue).toEqual({ - A: true, - B: true, - C: true, + }); }); - }); - it('ignores any capability type mutation from the switcher', async () => { - const caps = { - ...defaultCaps, - section: { + it('ignores any capability type mutation from the switcher', async () => { + const caps = { + ...defaultCaps, + section: { + boolean: true, + record: { + entry: true, + }, + }, + }; + const switcher = (req: KibanaRequest, capabilities: Capabilities) => ({ + section: { + boolean: { + entry: false, + }, + record: false, + }, + }); + const result = await getCapabilitiesResolver( + () => caps, + () => [ + { + switcher, + capabilityPath: ['*'], + }, + ] + )({ + request, + capabilityPath: ['*'], + applications: [], + useDefaultCapabilities: false, + }); + expect(result.section).toEqual({ boolean: true, record: { entry: true, }, - }, + }); + }); + }); + + describe('multiple switchers', () => { + const getCapabilities = (overrides: Partial): Capabilities => { + return { + ...defaultCaps, + ...overrides, + } as Capabilities; }; - const switcher = (req: KibanaRequest, capabilities: Capabilities) => ({ - section: { - boolean: { - entry: false, + + it('applies multiple switchers', async () => { + const caps = getCapabilities({ + section: { + entryA: true, + entryB: true, + entryC: true, + }, + }); + + const switcherA: SwitcherWithOptions = { + switcher: (req: KibanaRequest, capabilities: Capabilities) => ({ + section: { + entryA: false, + }, + }), + capabilityPath: ['*'], + }; + const switcherB: SwitcherWithOptions = { + switcher: (req: KibanaRequest, capabilities: Capabilities) => ({ + section: { + entryB: false, + }, + }), + capabilityPath: ['*'], + }; + + const result = await getCapabilitiesResolver( + () => caps, + () => [switcherA, switcherB] + )({ + request, + capabilityPath: ['*'], + applications: [], + useDefaultCapabilities: false, + }); + + expect(result.section).toEqual({ + entryA: false, + entryB: false, + entryC: true, + }); + }); + + it('only applies the switchers intersecting with the requested paths', async () => { + const caps = getCapabilities({ + section: { + entryA: true, + entryB: true, + entryC: true, }, - record: false, - }, + }); + + const switcherAFunc = jest.fn().mockResolvedValue({}); + const switcherBFunc = jest.fn().mockResolvedValue({}); + const switcherCFunc = jest.fn().mockResolvedValue({}); + + const switcherA: SwitcherWithOptions = { + switcher: switcherAFunc, + capabilityPath: ['*'], + }; + const switcherB: SwitcherWithOptions = { + switcher: switcherBFunc, + capabilityPath: ['ml.*'], + }; + const switcherC: SwitcherWithOptions = { + switcher: switcherCFunc, + capabilityPath: ['fileUpload.*'], + }; + + await getCapabilitiesResolver( + () => caps, + () => [switcherA, switcherB, switcherC] + )({ + request, + capabilityPath: ['ml.*'], + applications: [], + useDefaultCapabilities: false, + }); + + expect(switcherAFunc).toHaveBeenCalledTimes(1); + expect(switcherBFunc).toHaveBeenCalledTimes(1); + expect(switcherCFunc).toHaveBeenCalledTimes(0); + }); + + it('returns full capabilities even if not all switchers were applied', async () => { + const caps = getCapabilities({ + section: { + entryA: true, + entryB: true, + entryC: true, + }, + }); + + const switcherA: SwitcherWithOptions = { + switcher: (req: KibanaRequest, capabilities: Capabilities) => ({ + section: { + entryA: false, + }, + }), + capabilityPath: ['section.entryA'], + }; + const switcherB: SwitcherWithOptions = { + switcher: (req: KibanaRequest, capabilities: Capabilities) => ({ + section: { + entryB: false, + }, + }), + capabilityPath: ['section.entryB'], + }; + + const result = await getCapabilitiesResolver( + () => caps, + () => [switcherA, switcherB] + )({ + request, + capabilityPath: ['section.entryA'], + applications: [], + useDefaultCapabilities: false, + }); + + expect(result.section).toEqual({ + entryA: false, + entryB: true, + entryC: true, + }); }); - const result = await resolveCapabilities(caps, [switcher], request, [], true); - expect(result.section).toEqual({ - boolean: true, - record: { - entry: true, - }, + }); + + describe('caching behavior', () => { + it('caches results between calls for the same capability path', async () => { + const resolver = getCapabilitiesResolver( + () => defaultCaps, + () => [] + ); + + await resolver({ + request, + capabilityPath: ['*'], + applications: [], + useDefaultCapabilities: false, + }); + + expect(splitIntoBucketsMock).toHaveBeenCalledTimes(1); + + await resolver({ + request, + capabilityPath: ['*'], + applications: [], + useDefaultCapabilities: false, + }); + + expect(splitIntoBucketsMock).toHaveBeenCalledTimes(1); + }); + + it('does not cache results between calls for different capability path', async () => { + const resolver = getCapabilitiesResolver( + () => defaultCaps, + () => [] + ); + + await resolver({ + request, + capabilityPath: ['*'], + applications: [], + useDefaultCapabilities: false, + }); + + expect(splitIntoBucketsMock).toHaveBeenCalledTimes(1); + + await resolver({ + request, + capabilityPath: ['ml.*'], + applications: [], + useDefaultCapabilities: false, + }); + + expect(splitIntoBucketsMock).toHaveBeenCalledTimes(2); + }); + + it('caches results between calls for the same capability paths', async () => { + const resolver = getCapabilitiesResolver( + () => defaultCaps, + () => [] + ); + + await resolver({ + request, + capabilityPath: ['ml.*', 'file.*'], + applications: [], + useDefaultCapabilities: false, + }); + + expect(splitIntoBucketsMock).toHaveBeenCalledTimes(1); + + await resolver({ + request, + capabilityPath: ['ml.*', 'file.*'], + applications: [], + useDefaultCapabilities: false, + }); + + expect(splitIntoBucketsMock).toHaveBeenCalledTimes(1); + }); + + it('does not cache results between calls for different capability paths', async () => { + const resolver = getCapabilitiesResolver( + () => defaultCaps, + () => [] + ); + + await resolver({ + request, + capabilityPath: ['ml.*', 'file.*'], + applications: [], + useDefaultCapabilities: false, + }); + + expect(splitIntoBucketsMock).toHaveBeenCalledTimes(1); + + await resolver({ + request, + capabilityPath: ['ml.*', 'not-file.*'], + applications: [], + useDefaultCapabilities: false, + }); + + expect(splitIntoBucketsMock).toHaveBeenCalledTimes(2); + }); + + it('does not cache results between calls from different resolvers', async () => { + const resolverA = getCapabilitiesResolver( + () => defaultCaps, + () => [] + ); + const resolverB = getCapabilitiesResolver( + () => defaultCaps, + () => [] + ); + + await resolverA({ + request, + capabilityPath: ['*'], + applications: [], + useDefaultCapabilities: false, + }); + + expect(splitIntoBucketsMock).toHaveBeenCalledTimes(1); + + await resolverB({ + request, + capabilityPath: ['*'], + applications: [], + useDefaultCapabilities: false, + }); + + expect(splitIntoBucketsMock).toHaveBeenCalledTimes(2); }); }); }); diff --git a/packages/core/capabilities/core-capabilities-server-internal/src/resolve_capabilities.ts b/packages/core/capabilities/core-capabilities-server-internal/src/resolve_capabilities.ts index f30392a2213ba4..b1dc24ef64d061 100644 --- a/packages/core/capabilities/core-capabilities-server-internal/src/resolve_capabilities.ts +++ b/packages/core/capabilities/core-capabilities-server-internal/src/resolve_capabilities.ts @@ -6,46 +6,92 @@ * Side Public License, v 1. */ -import { cloneDeep } from 'lodash'; +import { cloneDeep, memoize, uniqueId } from 'lodash'; import { withSpan } from '@kbn/apm-utils'; +import type { CapabilitiesSwitcher } from '@kbn/core-capabilities-server'; import type { KibanaRequest } from '@kbn/core-http-server'; import type { Capabilities } from '@kbn/core-capabilities-common'; -import type { CapabilitiesSwitcher } from '@kbn/core-capabilities-server'; +import type { SwitcherWithOptions, SwitcherWithId } from './types'; +import { pathsIntersect, splitIntoBuckets, convertBucketToSwitcher } from './resolve_helpers'; -export type CapabilitiesResolver = ( - request: KibanaRequest, - applications: string[], - useDefaultCapabilities: boolean -) => Promise; +export type CapabilitiesResolver = ({ + request, + capabilityPath, + applications, + useDefaultCapabilities, +}: { + request: KibanaRequest; + capabilityPath: string[]; + applications: string[]; + useDefaultCapabilities: boolean; +}) => Promise; + +type ForPathSwitcherResolver = (path: string, switchers: SwitcherWithId[]) => string[]; +type AggregatedSwitchersResolver = (capabilityPaths: string[]) => CapabilitiesSwitcher[]; + +export const getCapabilitiesResolver = ( + getCapabilities: () => Capabilities, + getSwitchers: () => SwitcherWithOptions[] +): CapabilitiesResolver => { + let initialized = false; + let capabilities: Capabilities; + let switchers: Map; + // memoize is on the first argument only by default, which is what we want here + const getSwitcherForPath: ForPathSwitcherResolver = memoize(getSwitchersToUseForPath); + let getAggregatedSwitchers: AggregatedSwitchersResolver; + + return async ({ + request, + capabilityPath, + applications, + useDefaultCapabilities, + }): Promise => { + // initialize on first call (can't do it before as we need to wait plugins start to complete) + if (!initialized) { + capabilities = getCapabilities(); + switchers = new Map(); + getSwitchers().forEach((switcher) => { + const switcherId = uniqueId('s-'); + switchers.set(switcherId, { + id: switcherId, + ...switcher, + }); + }); + getAggregatedSwitchers = memoize( + buildGetAggregatedSwitchers(getSwitcherForPath, switchers), + (capabilityPaths: string[]) => capabilityPaths.join('|') + ); + initialized = true; + } -export const getCapabilitiesResolver = - ( - capabilities: () => Capabilities, - switchers: () => CapabilitiesSwitcher[] - ): CapabilitiesResolver => - async ( - request: KibanaRequest, - applications: string[], - useDefaultCapabilities: boolean - ): Promise => { return withSpan({ name: 'resolve capabilities', type: 'capabilities' }, () => - resolveCapabilities( - capabilities(), - switchers(), + resolveCapabilities({ + capabilities, request, + capabilityPath, applications, - useDefaultCapabilities - ) + useDefaultCapabilities, + getAggregatedSwitchers, + }) ); }; +}; -export const resolveCapabilities = async ( - capabilities: Capabilities, - switchers: CapabilitiesSwitcher[], - request: KibanaRequest, - applications: string[], - useDefaultCapabilities: boolean -): Promise => { +const resolveCapabilities = async ({ + capabilities, + request, + capabilityPath, + applications, + useDefaultCapabilities, + getAggregatedSwitchers, +}: { + capabilities: Capabilities; + request: KibanaRequest; + applications: string[]; + capabilityPath: string[]; + useDefaultCapabilities: boolean; + getAggregatedSwitchers: AggregatedSwitchersResolver; +}): Promise => { const mergedCaps: Capabilities = cloneDeep({ ...capabilities, navLinks: applications.reduce((acc, app) => { @@ -54,6 +100,8 @@ export const resolveCapabilities = async ( }, capabilities.navLinks), }); + const switchers = getAggregatedSwitchers(capabilityPath); + return switchers.reduce(async (caps, switcher) => { const resolvedCaps = await caps; const changes = await switcher(request, resolvedCaps, useDefaultCapabilities); @@ -82,3 +130,38 @@ function recursiveApplyChanges< return acc; }, {} as TDestination); } + +const getSwitchersToUseForPath = (path: string, switchers: SwitcherWithId[]): string[] => { + const switcherIds: string[] = []; + switchers.forEach((switcher) => { + if (switcher.capabilityPath.some((switcherPath) => pathsIntersect(path, switcherPath))) { + switcherIds.push(switcher.id); + } + }); + return switcherIds; +}; + +const buildGetAggregatedSwitchers = + ( + getSwitcherForPath: ForPathSwitcherResolver, + switcherMap: Map + ): AggregatedSwitchersResolver => + (capabilityPaths: string[]): CapabilitiesSwitcher[] => { + // find switchers that should be applied for the provided capabilityPaths + const allSwitchers = [...switcherMap.values()]; + const switcherIdsToApply = new Set(); + capabilityPaths.forEach((path) => { + getSwitcherForPath(path, allSwitchers).forEach((switcherId) => + switcherIdsToApply.add(switcherId) + ); + }); + const switchersToApply = [...switcherIdsToApply].map( + (switcherId) => switcherMap.get(switcherId)! + ); + + // split the switchers into buckets for parallel execution + const switcherBuckets = splitIntoBuckets(switchersToApply); + + // convert the multi-switcher buckets into switchers + return switcherBuckets.map(convertBucketToSwitcher); + }; diff --git a/packages/core/capabilities/core-capabilities-server-internal/src/resolve_helpers.test.ts b/packages/core/capabilities/core-capabilities-server-internal/src/resolve_helpers.test.ts new file mode 100644 index 00000000000000..6e3e6164505d09 --- /dev/null +++ b/packages/core/capabilities/core-capabilities-server-internal/src/resolve_helpers.test.ts @@ -0,0 +1,172 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { httpServerMock } from '@kbn/core-http-server-mocks'; +import type { Capabilities } from '@kbn/core-capabilities-common'; +import type { CapabilitiesSwitcher } from '@kbn/core-capabilities-server'; +import { pathsIntersect, splitIntoBuckets, convertBucketToSwitcher } from './resolve_helpers'; +import type { SwitcherWithId, SwitcherBucket } from './types'; + +describe('pathsIntersect', () => { + test.each([ + ['*', '*', true], + ['foo', 'foo', true], + ['foo.*', '*', true], + ['bar', '*', true], + ['foo.bar', '*', true], + ['foo.bar', 'foo.*', true], + ['foo.bar', 'foo.bar', true], + ['foo', 'bar', false], + ['foo.*', 'bar.*', false], + ['foo.bar', 'bar.*', false], + ['common.foo', 'common.bar', false], + ['common.foo.*', 'common.bar.*', false], + ])('%p and %p returns %p', (pathA, pathB, expected) => { + expect(pathsIntersect(pathA, pathB)).toBe(expected); + expect(pathsIntersect(pathB, pathA)).toBe(expected); + }); +}); + +describe('splitIntoBuckets', () => { + const extractIds = (buckets: SwitcherBucket[]): string[][] => { + return buckets.map((bucket) => bucket.switchers.map((switcher) => switcher.id)); + }; + + const switcherWithId = (id: string, paths: string | string[]): SwitcherWithId => { + return { + id, + capabilityPath: Array.isArray(paths) ? paths : [paths], + switcher: jest.fn(), + }; + }; + + it('properly dispatch the switchers, simple case', () => { + const switchers = [ + switcherWithId('1', ['*']), + switcherWithId('2', ['foo.*']), + switcherWithId('3', ['*']), + switcherWithId('4', ['bar.*']), + ]; + + const result = splitIntoBuckets(switchers); + expect(extractIds(result)).toEqual([['1'], ['2', '4'], ['3']]); + }); + + it('properly dispatch the switchers, more advanced case', () => { + const switchers = [ + switcherWithId('1', ['*']), + switcherWithId('2', ['foo.*']), + switcherWithId('3', ['foo.hello.*']), + switcherWithId('4', ['bar.*']), + switcherWithId('5', ['*']), + switcherWithId('6', ['bar.*']), + ]; + + const result = splitIntoBuckets(switchers); + expect(extractIds(result)).toEqual([['1'], ['2', '4'], ['3', '6'], ['5']]); + }); +}); + +describe('convertBucketToSwitcher', () => { + const switcherFromFn = (fn: CapabilitiesSwitcher): SwitcherWithId => { + return { + id: '42', + capabilityPath: ['*'], + switcher: fn, + }; + }; + + test('the underlying switchers are all called', async () => { + const switcher1 = jest.fn(); + const switcher2 = jest.fn(); + const switcher3 = jest.fn(); + + const bucket: SwitcherBucket = { + bucketPaths: new Set(['*']), + switchers: [switcherFromFn(switcher1), switcherFromFn(switcher2), switcherFromFn(switcher3)], + }; + + const switcher = convertBucketToSwitcher(bucket); + + const request = httpServerMock.createKibanaRequest(); + await switcher(request, {} as Capabilities, false); + + expect(switcher1).toHaveBeenCalledTimes(1); + expect(switcher2).toHaveBeenCalledTimes(1); + expect(switcher3).toHaveBeenCalledTimes(1); + }); + + test('the underlying switchers are called with the correct arguments', async () => { + const switcher1 = jest.fn(); + const switcher2 = jest.fn(); + const switcher3 = jest.fn(); + + const bucket: SwitcherBucket = { + bucketPaths: new Set(['*']), + switchers: [switcherFromFn(switcher1), switcherFromFn(switcher2), switcherFromFn(switcher3)], + }; + + const switcher = convertBucketToSwitcher(bucket); + + const request = httpServerMock.createKibanaRequest(); + const capabilities = { navLinks: { bar: false } } as unknown as Capabilities; + await switcher(request, capabilities, false); + + expect(switcher1).toHaveBeenCalledWith(request, capabilities, false); + expect(switcher2).toHaveBeenCalledWith(request, capabilities, false); + expect(switcher3).toHaveBeenCalledWith(request, capabilities, false); + }); + + test('returns the aggregated result from all the underlying switchers', async () => { + const switcher1 = jest.fn().mockResolvedValue({ foo: { bar: 1 } }); + const switcher2 = jest.fn().mockResolvedValue({ bar: { hello: 2 } }); + const switcher3 = jest.fn().mockResolvedValue({ hello: { dolly: 3 } }); + + const bucket: SwitcherBucket = { + bucketPaths: new Set(['*']), + switchers: [switcherFromFn(switcher1), switcherFromFn(switcher2), switcherFromFn(switcher3)], + }; + + const switcher = convertBucketToSwitcher(bucket); + + const request = httpServerMock.createKibanaRequest(); + const capabilities = { navLinks: { bar: false } } as unknown as Capabilities; + const changes = await switcher(request, capabilities, false); + + expect(changes).toEqual({ + foo: { bar: 1 }, + bar: { hello: 2 }, + hello: { dolly: 3 }, + }); + }); + + test('result aggregation works even for non-intersecting nested values', async () => { + const switcher1 = jest.fn().mockResolvedValue({ nested: { foo: 1 } }); + const switcher2 = jest.fn().mockResolvedValue({ nested: { bar: 2 } }); + const switcher3 = jest.fn().mockResolvedValue({ nested: { dolly: 3 } }); + + const bucket: SwitcherBucket = { + bucketPaths: new Set(['*']), + switchers: [switcherFromFn(switcher1), switcherFromFn(switcher2), switcherFromFn(switcher3)], + }; + + const switcher = convertBucketToSwitcher(bucket); + + const request = httpServerMock.createKibanaRequest(); + const capabilities = { navLinks: { bar: false } } as unknown as Capabilities; + const changes = await switcher(request, capabilities, false); + + expect(changes).toEqual({ + nested: { + foo: 1, + bar: 2, + dolly: 3, + }, + }); + }); +}); diff --git a/packages/core/capabilities/core-capabilities-server-internal/src/resolve_helpers.ts b/packages/core/capabilities/core-capabilities-server-internal/src/resolve_helpers.ts new file mode 100644 index 00000000000000..5b183ec52b530d --- /dev/null +++ b/packages/core/capabilities/core-capabilities-server-internal/src/resolve_helpers.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { merge } from 'lodash'; +import type { CapabilitiesSwitcher } from '@kbn/core-capabilities-server'; +import type { SwitcherWithId, SwitcherBucket } from './types'; + +/** + * Returns true if the two globing paths can intersect. + * + * @example + * ```ts + * pathsIntersect('*', '*'); // true + * pathsIntersect('*', 'foo.bar'); // true + * pathsIntersect('foo.*', 'bar.*'); // false + * ``` + * + * @internal + */ +export const pathsIntersect = (pathA: string, pathB: string): boolean => { + const splitA = pathA.split('.'); + const splitB = pathB.split('.'); + const minLength = Math.min(splitA.length, splitB.length); + + for (let i = 0; i < minLength; i++) { + const segA = splitA[i]; + const segB = splitB[i]; + if (segA === '*' || segB === '*') { + return true; + } + if (segA !== segB) { + return false; + } + } + return splitA.length === splitB.length; +}; + +/** + * Splits the provided switchers into buckets so that switchers allocated + * into a given buckets can all be executed in parallel. + * (each switcher in a given bucket doesn't intersect with any other switcher of the same bucket) + * + * @internal + */ +export const splitIntoBuckets = (switchers: SwitcherWithId[]): SwitcherBucket[] => { + const buckets: SwitcherBucket[] = []; + + const canBeAddedToBucket = (switcher: SwitcherWithId, bucket: SwitcherBucket): boolean => { + const bucketPaths = [...bucket.bucketPaths]; + for (const switcherPath of switcher.capabilityPath) { + for (const bucketPath of bucketPaths) { + if (pathsIntersect(switcherPath, bucketPath)) { + return false; + } + } + } + return true; + }; + + const addIntoBucket = (switcher: SwitcherWithId, bucket: SwitcherBucket) => { + bucket.switchers.push(switcher); + switcher.capabilityPath.forEach((path) => { + bucket.bucketPaths.add(path); + }); + }; + + for (const switcher of switchers) { + let added = false; + for (const bucket of buckets) { + // switcher can be added -> we do and we break + if (canBeAddedToBucket(switcher, bucket)) { + addIntoBucket(switcher, bucket); + added = true; + break; + } + } + // could not find a bucket to add the switch to -> creating a new one + if (!added) { + buckets.push({ + switchers: [switcher], + bucketPaths: new Set(switcher.capabilityPath), + }); + } + } + + return buckets; +}; + +/** + * Aggregates all the switchers of the given bucket to a single switcher function. + * Only works under the assumption that the switchers in the bucket don't intersect + * (But that's the definition of a switcher bucket) + * + * @internal + */ +export const convertBucketToSwitcher = (bucket: SwitcherBucket): CapabilitiesSwitcher => { + // only one switcher in the bucket -> no need to wrap + if (bucket.switchers.length === 1) { + return bucket.switchers[0].switcher; + } + + const switchers = bucket.switchers.map((switcher) => switcher.switcher); + + return async (request, uiCapabilities, useDefaultCapabilities) => { + const allChanges = await Promise.all( + switchers.map((switcher) => { + return switcher(request, uiCapabilities, useDefaultCapabilities); + }) + ); + return merge({}, ...allChanges); + }; +}; diff --git a/packages/core/capabilities/core-capabilities-server-internal/src/routes/resolve_capabilities.ts b/packages/core/capabilities/core-capabilities-server-internal/src/routes/resolve_capabilities.ts index 1b29473801e490..48b182aa610163 100644 --- a/packages/core/capabilities/core-capabilities-server-internal/src/routes/resolve_capabilities.ts +++ b/packages/core/capabilities/core-capabilities-server-internal/src/routes/resolve_capabilities.ts @@ -39,7 +39,12 @@ export function registerCapabilitiesRoutes(router: IRouter, resolver: Capabiliti async (ctx, req, res) => { const { useDefaultCapabilities } = req.query; const { applications } = req.body; - const capabilities = await resolver(req, applications, useDefaultCapabilities); + const capabilities = await resolver({ + request: req, + applications, + useDefaultCapabilities, + capabilityPath: ['*'], + }); return res.ok({ body: capabilities, }); diff --git a/packages/core/capabilities/core-capabilities-server-internal/src/types.ts b/packages/core/capabilities/core-capabilities-server-internal/src/types.ts new file mode 100644 index 00000000000000..dcf8b03d57c509 --- /dev/null +++ b/packages/core/capabilities/core-capabilities-server-internal/src/types.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { CapabilitiesSwitcher } from '@kbn/core-capabilities-server'; + +export interface SwitcherWithOptions { + switcher: CapabilitiesSwitcher; + capabilityPath: string[]; +} + +export interface SwitcherWithId extends SwitcherWithOptions { + id: string; +} + +export interface SwitcherBucket { + switchers: SwitcherWithId[]; + bucketPaths: Set; +} diff --git a/packages/core/capabilities/core-capabilities-server/index.ts b/packages/core/capabilities/core-capabilities-server/index.ts index 16fe5750725210..286fa8e80e907c 100644 --- a/packages/core/capabilities/core-capabilities-server/index.ts +++ b/packages/core/capabilities/core-capabilities-server/index.ts @@ -11,4 +11,5 @@ export type { CapabilitiesSetup, CapabilitiesStart, ResolveCapabilitiesOptions, + CapabilitiesSwitcherOptions, } from './src/contracts'; diff --git a/packages/core/capabilities/core-capabilities-server/src/contracts.ts b/packages/core/capabilities/core-capabilities-server/src/contracts.ts index 8f89b2fc777d41..9dfc5995dd7d59 100644 --- a/packages/core/capabilities/core-capabilities-server/src/contracts.ts +++ b/packages/core/capabilities/core-capabilities-server/src/contracts.ts @@ -61,7 +61,7 @@ export interface CapabilitiesSetup { * ```ts * // my-plugin/server/plugin.ts * public setup(core: CoreSetup, deps: {}) { - * core.capabilities.registerSwitcher((request, capabilities, useDefaultCapabilities) => { + * core.capabilities.registerSwitcher(async (request, capabilities, useDefaultCapabilities) => { * // useDefaultCapabilities is a special case that switchers typically don't have to concern themselves with. * // The default capabilities are typically the ones you provide in your CapabilitiesProvider, but this flag * // gives each switcher an opportunity to change the default capabilities of other plugins' capabilities. @@ -74,7 +74,7 @@ export interface CapabilitiesSetup { * } * } * } - * if(myPluginApi.shouldRestrictSomePluginBecauseOf(request)) { + * if(await myPluginApi.shouldRestrictSomePluginBecauseOf(request)) { * return { * somePlugin: { * featureEnabledByDefault: false // `featureEnabledByDefault` will be disabled. All other capabilities will remain unchanged. @@ -82,36 +82,73 @@ export interface CapabilitiesSetup { * } * } * return {}; // All capabilities will remain unchanged. + * }, { + * // the switcher only toggles capabilities under the 'somePlugin' path + * capabilityPath: 'somePlugin', * }); * } * ``` */ - registerSwitcher(switcher: CapabilitiesSwitcher): void; + registerSwitcher(switcher: CapabilitiesSwitcher, options: CapabilitiesSwitcherOptions): void; } /** - * Defines a set of additional options for the `resolveCapabilities` method of {@link CapabilitiesStart}. + * APIs to access the application {@link Capabilities}. + * + * @public + */ +export interface CapabilitiesStart { + /** + * Resolve the {@link Capabilities} to be used for given request + * + * @param request The request to resolve capabilities for + * @param options.capabilityPath The path(s) of the capabilities that needs to be retrieved. Use '*' to retrieve all paths. + * Used to avoid unnecessarily running switched on parts of the capabilities that won't be used by the API consumer. + * + * @example + * ```ts + * const mlCapabilities = (await coreStart.capabilities.resolveCapabilities(request, 'ml')).ml; + * ``` + */ + resolveCapabilities( + request: KibanaRequest, + options: ResolveCapabilitiesOptions + ): Promise; +} + +/** + * Options for {@link CapabilitiesStart.resolveCapabilities}. * * @public */ export interface ResolveCapabilitiesOptions { + /** + * The path(s) of capabilities that the API consumer is interested in. The '*' wildcard is supported as a suffix only. + * + * E.g. capabilityPath: "*" or capabilityPath: "myPlugin.*" or capabilityPath: "myPlugin.myKey" + * + * @remark All the capabilities will be returned, but the ones not matching the specified path(s) may not have been processed + * by the capability switchers and should not be used. + */ + capabilityPath: string | string[]; /** * Indicates if capability switchers are supposed to return a default set of capabilities. + * + * Defaults to `false` */ - useDefaultCapabilities: boolean; + useDefaultCapabilities?: boolean; } /** - * APIs to access the application {@link Capabilities}. + * Options for the {@link CapabilitiesSetup.registerSwitcher} API. * * @public */ -export interface CapabilitiesStart { +export interface CapabilitiesSwitcherOptions { /** - * Resolve the {@link Capabilities} to be used for given request + * The path(s) of capabilities the switcher may alter. The '*' wildcard is supported as a suffix only. + * + * E.g. capabilityPath: "myPlugin.*" or capabilityPath: "myPlugin.myKey" */ - resolveCapabilities( - request: KibanaRequest, - options?: ResolveCapabilitiesOptions - ): Promise; + capabilityPath: string | string[]; } diff --git a/packages/core/chrome/core-chrome-browser-internal/src/ui/project/header.tsx b/packages/core/chrome/core-chrome-browser-internal/src/ui/project/header.tsx index 0892b0c3639118..30fe35be0b5514 100644 --- a/packages/core/chrome/core-chrome-browser-internal/src/ui/project/header.tsx +++ b/packages/core/chrome/core-chrome-browser-internal/src/ui/project/header.tsx @@ -196,10 +196,6 @@ export const ProjectHeader = ({ /> - - - - diff --git a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/configure_client.ts b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/configure_client.ts index 5f8d793b0a4264..287f3a670ce488 100644 --- a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/configure_client.ts +++ b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/configure_client.ts @@ -13,9 +13,13 @@ import { parseClientOptions } from './client_config'; import { instrumentEsQueryAndDeprecationLogger } from './log_query_and_deprecation'; import { createTransport } from './create_transport'; import type { AgentFactoryProvider } from './agent_manager'; +import { patchElasticsearchClient } from './patch_client'; const noop = () => undefined; +// Apply ES client patches on module load +patchElasticsearchClient(); + export const configureClient = ( config: ElasticsearchClientConfig, { diff --git a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/patch_client.ts b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/patch_client.ts new file mode 100644 index 00000000000000..75ea5db91b2e34 --- /dev/null +++ b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/patch_client.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { errors } from '@elastic/elasticsearch'; + +export const patchElasticsearchClient = () => { + const baseErrorPrototype = errors.ElasticsearchClientError.prototype; + // @ts-expect-error + baseErrorPrototype.toJSON = function () { + return { + name: this.name, + message: this.message, + }; + }; +}; diff --git a/packages/core/http/core-http-router-server-internal/src/versioned_router/types.ts b/packages/core/http/core-http-router-server-internal/src/versioned_router/types.ts index 4004004036c53c..b085593cb0a5e3 100644 --- a/packages/core/http/core-http-router-server-internal/src/versioned_router/types.ts +++ b/packages/core/http/core-http-router-server-internal/src/versioned_router/types.ts @@ -15,15 +15,15 @@ import type { export type Method = Exclude; -/** @experimental */ +/** @internal */ export interface VersionedRouterRoute { - /** @experimental */ + /** @internal */ method: string; - /** @experimental */ + /** @internal */ path: string; - /** @experimental */ + /** @internal */ options: VersionedRouteConfig; - /** @experimental */ + /** @internal */ handlers: Array<{ fn: RequestHandler; options: AddVersionOpts; diff --git a/packages/core/http/core-http-server/src/versioning/types.ts b/packages/core/http/core-http-server/src/versioning/types.ts index 265a99eb33db7c..f295fbaf0e5347 100644 --- a/packages/core/http/core-http-server/src/versioning/types.ts +++ b/packages/core/http/core-http-server/src/versioning/types.ts @@ -26,7 +26,7 @@ export type { ApiVersion }; /** * Configuration for a versioned route - * @experimental + * @public */ export type VersionedRouteConfig = Omit< RouteConfig, @@ -49,7 +49,7 @@ export type VersionedRouteConfig = Omit< * @note When enabled `apiVersion` is a reserved query parameter and will not * be passed to the route handler or handler validation. * @note `apiVersion` is a reserved query parameter, avoid using it - * @experimental + * @public * @default false */ enableQueryVersion?: boolean; @@ -60,7 +60,7 @@ export type VersionedRouteConfig = Omit< * * @param config - The route configuration * @returns A versioned route - * @experimental + * @public */ export type VersionedRouteRegistrar = ( config: VersionedRouteConfig @@ -155,40 +155,40 @@ export type VersionedRouteRegistrar { /** - * @experimental + * @public * @track-adoption */ get: VersionedRouteRegistrar<'get', Ctx>; /** - * @experimental + * @public * @track-adoption */ put: VersionedRouteRegistrar<'put', Ctx>; /** - * @experimental + * @public * @track-adoption */ post: VersionedRouteRegistrar<'post', Ctx>; /** - * @experimental + * @public * @track-adoption */ patch: VersionedRouteRegistrar<'patch', Ctx>; /** - * @experimental + * @public * @track-adoption */ delete: VersionedRouteRegistrar<'delete', Ctx>; } -/** @experimental */ +/** @public */ export type VersionedRouteRequestValidation = RouteValidatorFullConfig; -/** @experimental */ +/** @public */ export interface VersionedRouteResponseValidation { [statusCode: number]: { body: RouteValidationFunction | Type }; unsafe?: { body?: boolean }; @@ -196,19 +196,19 @@ export interface VersionedRouteResponseValidation { /** * Versioned route validation - * @experimental + * @public */ export interface FullValidationConfig { /** * Validation to run against route inputs: params, query and body - * @experimental + * @public */ request?: VersionedRouteRequestValidation; /** * Validation to run against route output * @note This validation is only intended to run in development. Do not use this * for setting default values! - * @experimental + * @public */ response?: VersionedRouteResponseValidation; } @@ -216,24 +216,24 @@ export interface FullValidationConfig { /** * Options for a versioned route. Probably needs a lot more options like sunsetting * of an endpoint etc. - * @experimental + * @public */ export interface AddVersionOpts { /** * Version to assign to this route - * @experimental + * @public */ version: ApiVersion; /** * Validation for this version of a route - * @experimental + * @public */ validate: false | FullValidationConfig; } /** * A versioned route - * @experimental + * @public */ export interface VersionedRoute< Method extends RouteMethod = RouteMethod, @@ -244,7 +244,7 @@ export interface VersionedRoute< * @param opts {@link AddVersionOpts | Options} for this version of a route * @param handler The request handler for this version of a route * @returns A versioned route, allows for fluent chaining of version declarations - * @experimental + * @public */ addVersion

( options: AddVersionOpts, diff --git a/packages/core/logging/core-logging-common-internal/src/layouts/conversions/message.test.ts b/packages/core/logging/core-logging-common-internal/src/layouts/conversions/message.test.ts index cf16b57c927823..2c84635d6711c8 100644 --- a/packages/core/logging/core-logging-common-internal/src/layouts/conversions/message.test.ts +++ b/packages/core/logging/core-logging-common-internal/src/layouts/conversions/message.test.ts @@ -62,4 +62,52 @@ describe('MessageConversion', () => { '\\u001b\\u0000[31mESC-INJECTION-LFUNICODE:\\u001b[32mSUCCESSFUL\\u001b[0m\\u0007\n\nInjecting 10.000 lols 😂\\u001b[10000;b\\u0007' ); }); + + test('it should encode/escape ANSI chars lines from the message when not a string', () => { + expect( + MessageConversion.convert( + { + ...baseRecord, + // @ts-expect-error message is supposed to be a string + message: { + toString: () => 'toString...\u001b[5;7;6mThis is Fine\u001b[27m', + }, + }, + false + ) + ).toEqual('toString...\\u001b[5;7;6mThis is Fine\\u001b[27m'); + }); + + test('it should encode/escape ANSI chars lines from the error stack', () => { + const error = new Error('Something went bad'); + error.stack = 'stack...\u001b[5;7;6mThis is Fine\u001b[27m'; + expect( + MessageConversion.convert( + { + ...baseRecord, + message: 'Some message that will be ignored', + error, + }, + false + ) + ).toEqual('stack...\\u001b[5;7;6mThis is Fine\\u001b[27m'); + }); + + test('it should encode/escape ANSI chars lines from the error stack when not a string', () => { + expect( + MessageConversion.convert( + { + ...baseRecord, + message: 'Some message that will be ignored', + error: { + // @ts-expect-error message is supposed to be a string + stack: { + toString: () => 'stackToString...\u001b[5;7;6mThis is Fine\u001b[27m', + }, + }, + }, + false + ) + ).toEqual('stackToString...\\u001b[5;7;6mThis is Fine\\u001b[27m'); + }); }); diff --git a/packages/core/logging/core-logging-common-internal/src/layouts/conversions/message.ts b/packages/core/logging/core-logging-common-internal/src/layouts/conversions/message.ts index 706aa478d992b1..1d0dd15373aafc 100644 --- a/packages/core/logging/core-logging-common-internal/src/layouts/conversions/message.ts +++ b/packages/core/logging/core-logging-common-internal/src/layouts/conversions/message.ts @@ -17,17 +17,19 @@ export const MessageConversion: Conversion = { pattern: /%message/g, convert(record: LogRecord) { // Error stack is much more useful than just the message. - const str = record.error?.stack || record.message; + let str = record.error?.stack || record.message; + // typings may be wrong, there's scenarios where the message is not a plain string (e.g error stacks from the ES client) + if (typeof str !== 'string') { + str = String(str); + } - return typeof str === 'string' // We need to validate it's a string because, despite types, there are use case where it's not a string :/ - ? str.replace( - CONTROL_CHAR_REGEXP, - // Escaping control chars via JSON.stringify to maintain consistency with `meta` and the JSON layout. - // This way, post analysis of the logs is easier as we can search the same patterns. - // Our benchmark didn't show a big difference in performance between custom-escaping vs. JSON.stringify one. - // The slice is removing the double-quotes. - (substr) => JSON.stringify(substr).slice(1, -1) - ) - : str; + return str.replace( + CONTROL_CHAR_REGEXP, + // Escaping control chars via JSON.stringify to maintain consistency with `meta` and the JSON layout. + // This way, post analysis of the logs is easier as we can search the same patterns. + // Our benchmark didn't show a big difference in performance between custom-escaping vs. JSON.stringify one. + // The slice is removing the double-quotes. + (substr) => JSON.stringify(substr).slice(1, -1) + ); }, }; diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search/aggregations/validation.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search/aggregations/validation.ts index d5dfbb8e8cb1dc..d058feb31ba78b 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search/aggregations/validation.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search/aggregations/validation.ts @@ -98,13 +98,14 @@ const validateAggregationContainer = ( ) => { return Object.entries(container).reduce( (memo, [aggName, aggregation]) => { - if (aggregationKeys.includes(aggName)) { - return memo; + if (!aggregationKeys.includes(aggName)) { + (memo as Record)[aggName] = validateAggregationType( + aggName, + aggregation, + childContext(context, aggName) + ); } - return { - ...memo, - [aggName]: validateAggregationType(aggName, aggregation, childContext(context, aggName)), - }; + return memo; }, {} ); diff --git a/packages/deeplinks/observability/kibana.jsonc b/packages/deeplinks/observability/kibana.jsonc index 4a93ff4427da87..bc014b05aa4072 100644 --- a/packages/deeplinks/observability/kibana.jsonc +++ b/packages/deeplinks/observability/kibana.jsonc @@ -1,5 +1,5 @@ { "type": "shared-common", "id": "@kbn/deeplinks-observability", - "owner": "@elastic/apm-ui" + "owner": "@elastic/obs-ux-logs-team" } diff --git a/packages/kbn-apm-synthtrace-client/kibana.jsonc b/packages/kbn-apm-synthtrace-client/kibana.jsonc index b9f2d79601d6db..99055f3da4fe9c 100644 --- a/packages/kbn-apm-synthtrace-client/kibana.jsonc +++ b/packages/kbn-apm-synthtrace-client/kibana.jsonc @@ -2,5 +2,5 @@ "type": "shared-common", "id": "@kbn/apm-synthtrace-client", "devOnly": true, - "owner": "@elastic/apm-ui" + "owner": "@elastic/obs-ux-infra_services-team" } diff --git a/packages/kbn-apm-synthtrace/kibana.jsonc b/packages/kbn-apm-synthtrace/kibana.jsonc index c1efc5fd09c00b..1b3ef5b8461495 100644 --- a/packages/kbn-apm-synthtrace/kibana.jsonc +++ b/packages/kbn-apm-synthtrace/kibana.jsonc @@ -2,5 +2,5 @@ "type": "shared-server", "id": "@kbn/apm-synthtrace", "devOnly": true, - "owner": "@elastic/apm-ui" + "owner": "@elastic/obs-ux-infra_services-team" } diff --git a/packages/kbn-apm-utils/kibana.jsonc b/packages/kbn-apm-utils/kibana.jsonc index 950a5dacb9ba86..2ee2a3b45335aa 100644 --- a/packages/kbn-apm-utils/kibana.jsonc +++ b/packages/kbn-apm-utils/kibana.jsonc @@ -1,5 +1,5 @@ { "type": "shared-common", "id": "@kbn/apm-utils", - "owner": "@elastic/apm-ui" + "owner": "@elastic/obs-ux-infra_services-team" } diff --git a/packages/kbn-cell-actions/src/hooks/use_data_grid_column_cell_actions.test.tsx b/packages/kbn-cell-actions/src/hooks/use_data_grid_column_cell_actions.test.tsx index 136ca441588f4d..014cb526c81fe7 100644 --- a/packages/kbn-cell-actions/src/hooks/use_data_grid_column_cell_actions.test.tsx +++ b/packages/kbn-cell-actions/src/hooks/use_data_grid_column_cell_actions.test.tsx @@ -13,8 +13,8 @@ import { EuiDataGridRefProps, type EuiDataGridColumnCellAction, } from '@elastic/eui'; -import { render, waitFor } from '@testing-library/react'; -import { act, renderHook } from '@testing-library/react-hooks'; +import { render, waitFor, act } from '@testing-library/react'; +import { renderHook } from '@testing-library/react-hooks'; import { makeAction } from '../mocks/helpers'; import { useDataGridColumnsCellActions, diff --git a/packages/kbn-config-schema/src/types/object_type.ts b/packages/kbn-config-schema/src/types/object_type.ts index fa72ee61ab7ff6..6927a10630b1eb 100644 --- a/packages/kbn-config-schema/src/types/object_type.ts +++ b/packages/kbn-config-schema/src/types/object_type.ts @@ -159,10 +159,7 @@ export class ObjectType

extends Type> ...newProps, }).reduce((memo, [key, value]) => { if (value !== null && value !== undefined) { - return { - ...memo, - [key]: value, - }; + (memo as Record)[key] = value; } return memo; }, {} as ExtendedProps); diff --git a/packages/kbn-custom-integrations/kibana.jsonc b/packages/kbn-custom-integrations/kibana.jsonc index 61c9067c7e6598..995d5bb4ef69d0 100644 --- a/packages/kbn-custom-integrations/kibana.jsonc +++ b/packages/kbn-custom-integrations/kibana.jsonc @@ -1,5 +1,5 @@ { "type": "shared-common", "id": "@kbn/custom-integrations", - "owner": "@elastic/infra-monitoring-ui" + "owner": "@elastic/obs-ux-logs-team" } diff --git a/packages/kbn-es-types/kibana.jsonc b/packages/kbn-es-types/kibana.jsonc index 1c00cab81d2c4d..2435d7666cf9a9 100644 --- a/packages/kbn-es-types/kibana.jsonc +++ b/packages/kbn-es-types/kibana.jsonc @@ -1,5 +1,5 @@ { "type": "shared-common", "id": "@kbn/es-types", - "owner": ["@elastic/kibana-core", "@elastic/apm-ui"] + "owner": ["@elastic/kibana-core", "@elastic/obs-knowledge-team"] } diff --git a/packages/kbn-es/README.mdx b/packages/kbn-es/README.mdx index 5927670030cdf9..eaacebdad54b5b 100644 --- a/packages/kbn-es/README.mdx +++ b/packages/kbn-es/README.mdx @@ -68,7 +68,7 @@ es.run({ Type: `String` -License type, one of: trial, basic, gold, platinum +License type, one of: basic, trial ##### options.version diff --git a/packages/kbn-es/src/artifact.test.js b/packages/kbn-es/src/artifact.test.js index b16cb3ab545481..221248caefae83 100644 --- a/packages/kbn-es/src/artifact.test.js +++ b/packages/kbn-es/src/artifact.test.js @@ -67,24 +67,18 @@ beforeEach(() => { MOCKS = { valid: { - archives: [createArchive({ license: 'oss' }), createArchive({ license: 'default' })], + archives: [createArchive({ license: 'default' })], }, invalidArch: { - archives: [ - createArchive({ license: 'oss', architecture: 'invalid_arch' }), - createArchive({ license: 'default', architecture: 'invalid_arch' }), - ], + archives: [createArchive({ license: 'default', architecture: 'invalid_arch' })], }, differentVersion: { - archives: [ - createArchive({ license: 'oss', version: 'another-version' }), - createArchive({ license: 'default', version: 'another-version' }), - ], + archives: [createArchive({ license: 'default', version: 'another-version' })], }, multipleArch: { archives: [ - createArchive({ architecture: 'fake_arch', license: 'oss' }), - createArchive({ architecture: ARCHITECTURE, license: 'oss' }), + createArchive({ architecture: 'fake_arch', license: 'default' }), + createArchive({ architecture: ARCHITECTURE, license: 'default' }), ], }, }; @@ -116,8 +110,6 @@ describe('Artifact', () => { mockFetch(MOCKS.valid); }); - it('should return artifact metadata for a daily oss artifact', artifactTest('oss', 'oss')); - it( 'should return artifact metadata for a daily default artifact', artifactTest('default', 'default') @@ -147,11 +139,6 @@ describe('Artifact', () => { mockFetch(MOCKS.valid); }); - it( - 'should return artifact metadata for a permanent oss artifact', - artifactTest('oss', 'oss', 2) - ); - it( 'should return artifact metadata for a permanent default artifact', artifactTest('default', 'default', 2) @@ -181,8 +168,8 @@ describe('Artifact', () => { }); it('should return artifact metadata for the correct architecture', async () => { - const artifact = await Artifact.getSnapshot('oss', MOCK_VERSION, log); - expect(artifact.spec.filename).toEqual(MOCK_FILENAME + `-${ARCHITECTURE}.oss`); + const artifact = await Artifact.getSnapshot('default', MOCK_VERSION, log); + expect(artifact.spec.filename).toEqual(MOCK_FILENAME + `-${ARCHITECTURE}.default`); }); }); @@ -195,7 +182,7 @@ describe('Artifact', () => { }); it('should use the custom URL when looking for a snapshot', async () => { - await Artifact.getSnapshot('oss', MOCK_VERSION, log); + await Artifact.getSnapshot('default', MOCK_VERSION, log); expect(fetch.mock.calls[0][0]).toEqual(CUSTOM_URL); }); @@ -211,7 +198,7 @@ describe('Artifact', () => { }); it('should use the daily unverified URL when looking for a snapshot', async () => { - await Artifact.getSnapshot('oss', MOCK_VERSION, log); + await Artifact.getSnapshot('default', MOCK_VERSION, log); expect(fetch.mock.calls[0][0]).toEqual( `${DAILY_SNAPSHOT_BASE_URL}/${MOCK_VERSION}/manifest-latest.json` ); diff --git a/packages/kbn-es/src/artifact.ts b/packages/kbn-es/src/artifact.ts index 57a0e6fd5db06a..3b11b6deac9cf8 100644 --- a/packages/kbn-es/src/artifact.ts +++ b/packages/kbn-es/src/artifact.ts @@ -27,7 +27,7 @@ const PERMANENT_SNAPSHOTS_BASE_URL = 'https://storage.googleapis.com/kibana-ci-es-snapshots-permanent'; type ChecksumType = 'sha512'; -export type ArtifactLicense = 'oss' | 'basic' | 'trial'; +export type ArtifactLicense = 'basic' | 'trial'; interface ArtifactManifest { id: string; @@ -122,7 +122,7 @@ async function getArtifactSpecForSnapshot( log: ToolingLog ): Promise { const desiredVersion = urlVersion.replace('-SNAPSHOT', ''); - const desiredLicense = license === 'oss' ? 'oss' : 'default'; + const desiredLicense = 'default'; const customManifestUrl = process.env.ES_SNAPSHOT_MANIFEST; const primaryManifestUrl = `${DAILY_SNAPSHOTS_BASE_URL}/${desiredVersion}/manifest-latest${ diff --git a/packages/kbn-es/src/cli_commands/build_snapshots.ts b/packages/kbn-es/src/cli_commands/build_snapshots.ts index cc730418af338d..458f515bb398a9 100644 --- a/packages/kbn-es/src/cli_commands/build_snapshots.ts +++ b/packages/kbn-es/src/cli_commands/build_snapshots.ts @@ -49,35 +49,33 @@ export const buildSnapshots: Command = { del.sync(outputDir); Fs.mkdirSync(outputDir, { recursive: true }); - for (const license of ['oss', 'trial']) { - for (const platform of ['darwin', 'win32', 'linux']) { - log.info('Building', platform, license === 'trial' ? 'default' : 'oss', 'snapshot'); - await log.indent(4, async () => { - const snapshotPath = await buildSnapshot({ - license, - sourcePath: options.sourcePath, - log, - platform, - }); + for (const platform of ['darwin', 'win32', 'linux']) { + log.info('Building', platform, 'default snapshot'); + await log.indent(4, async () => { + const snapshotPath = await buildSnapshot({ + license: 'trial', + sourcePath: options.sourcePath, + log, + platform, + }); - const filename = basename(snapshotPath); - const outputPath = resolve(outputDir, filename); - const hash = createHash('sha512'); - await pipelineAsync( - Fs.createReadStream(snapshotPath), - new Transform({ - transform(chunk, _, cb) { - hash.update(chunk); - cb(undefined, chunk); - }, - }), - Fs.createWriteStream(outputPath) - ); + const filename = basename(snapshotPath); + const outputPath = resolve(outputDir, filename); + const hash = createHash('sha512'); + await pipelineAsync( + Fs.createReadStream(snapshotPath), + new Transform({ + transform(chunk, _, cb) { + hash.update(chunk); + cb(undefined, chunk); + }, + }), + Fs.createWriteStream(outputPath) + ); - Fs.writeFileSync(`${outputPath}.sha512`, `${hash.digest('hex')} ${filename}`); - log.success('snapshot and shasum written to', outputPath); - }); - } + Fs.writeFileSync(`${outputPath}.sha512`, `${hash.digest('hex')} ${filename}`); + log.success('snapshot and shasum written to', outputPath); + }); } }, }; diff --git a/packages/kbn-es/src/cli_commands/snapshot.ts b/packages/kbn-es/src/cli_commands/snapshot.ts index cf8a5149bc892e..2395e480ee221a 100644 --- a/packages/kbn-es/src/cli_commands/snapshot.ts +++ b/packages/kbn-es/src/cli_commands/snapshot.ts @@ -23,7 +23,7 @@ export const snapshot: Command = { return dedent` Options: - --license Run with a 'oss', 'basic', or 'trial' license [default: ${license}] + --license Run with a 'basic' or 'trial' license [default: ${license}] --version Version of ES to download [default: ${defaults.version}] --base-path Path containing cache/installations [default: ${basePath}] --install-path Installation path, defaults to 'source' within base-path diff --git a/packages/kbn-es/src/cli_commands/source.ts b/packages/kbn-es/src/cli_commands/source.ts index 6916c082676c0d..3c50d50c501587 100644 --- a/packages/kbn-es/src/cli_commands/source.ts +++ b/packages/kbn-es/src/cli_commands/source.ts @@ -20,7 +20,7 @@ export const source: Command = { return dedent` Options: - --license Run with a 'oss', 'basic', or 'trial' license [default: ${license}] + --license Run with a 'basic' or 'trial' license [default: ${license}] --source-path Path to ES source [default: ${defaults['source-path']}] --base-path Path containing cache/installations [default: ${basePath}] --install-path Installation path, defaults to 'source' within base-path diff --git a/packages/kbn-es/src/custom_snapshots.ts b/packages/kbn-es/src/custom_snapshots.ts index f3e6d3ecaf857d..dcd6f5b2c84063 100644 --- a/packages/kbn-es/src/custom_snapshots.ts +++ b/packages/kbn-es/src/custom_snapshots.ts @@ -42,7 +42,7 @@ export function resolveCustomSnapshotUrl( const ext = process.platform === 'win32' ? 'zip' : 'tar.gz'; const os = process.platform === 'win32' ? 'windows' : process.platform; - const name = license === 'oss' ? 'elasticsearch-oss' : 'elasticsearch'; + const name = 'elasticsearch'; const overrideUrl = customSnapshotUrl .replace('{name}', name) .replace('{ext}', ext) diff --git a/packages/kbn-es/src/install/install_archive.ts b/packages/kbn-es/src/install/install_archive.ts index 352b0fe6007db3..78e200d4d47fb8 100644 --- a/packages/kbn-es/src/install/install_archive.ts +++ b/packages/kbn-es/src/install/install_archive.ts @@ -66,17 +66,15 @@ export async function installArchive(archive: string, options?: InstallArchiveOp fs.mkdirSync(tmpdir, { recursive: true }); log.info('created %s', chalk.bold(tmpdir)); - if (license !== 'oss') { - // starting in 6.3, security is disabled by default. Since we bootstrap - // the keystore, we can enable security ourselves. - await appendToConfig(installPath, 'xpack.security.enabled', 'true'); + // starting in 6.3, security is disabled by default. Since we bootstrap + // the keystore, we can enable security ourselves. + await appendToConfig(installPath, 'xpack.security.enabled', 'true'); - await appendToConfig(installPath, 'xpack.license.self_generated.type', license); - await configureKeystore(installPath, log, [ - ['bootstrap.password', password], - ...parseSettings(esArgs, { filter: SettingsFilter.SecureOnly }), - ]); - } + await appendToConfig(installPath, 'xpack.license.self_generated.type', license); + await configureKeystore(installPath, log, [ + ['bootstrap.password', password], + ...parseSettings(esArgs, { filter: SettingsFilter.SecureOnly }), + ]); return { installPath }; } diff --git a/packages/kbn-es/src/utils/build_snapshot.ts b/packages/kbn-es/src/utils/build_snapshot.ts index 46d14910a27c2e..ec20fc64d8f4b0 100644 --- a/packages/kbn-es/src/utils/build_snapshot.ts +++ b/packages/kbn-es/src/utils/build_snapshot.ts @@ -31,9 +31,6 @@ interface BuildSnapshotOptions { * :distribution:archives:darwin-tar:assemble * :distribution:archives:linux-tar:assemble * :distribution:archives:windows-zip:assemble - * :distribution:archives:oss-darwin-tar:assemble - * :distribution:archives:oss-linux-tar:assemble - * :distribution:archives:oss-windows-zip:assemble */ export async function buildSnapshot({ license, @@ -67,15 +64,13 @@ export async function buildSnapshot({ } export function archiveForPlatform(platform: NodeJS.Platform, license: string) { - const taskPrefix = license === 'oss' ? 'oss-' : ''; - switch (platform) { case 'darwin': - return { format: 'tar', ext: 'tar.gz', task: `${taskPrefix}darwin-tar`, platform: 'darwin' }; + return { format: 'tar', ext: 'tar.gz', task: 'darwin-tar', platform: 'darwin' }; case 'win32': - return { format: 'zip', ext: 'zip', task: `${taskPrefix}windows-zip`, platform: 'windows' }; + return { format: 'zip', ext: 'zip', task: 'windows-zip', platform: 'windows' }; case 'linux': - return { format: 'tar', ext: 'tar.gz', task: `${taskPrefix}linux-tar`, platform: 'linux' }; + return { format: 'tar', ext: 'tar.gz', task: 'linux-tar', platform: 'linux' }; default: throw new Error(`unsupported platform: ${platform}`); } diff --git a/packages/kbn-eslint-plugin-i18n/kibana.jsonc b/packages/kbn-eslint-plugin-i18n/kibana.jsonc index 7d8c994400108b..72e051941db68b 100644 --- a/packages/kbn-eslint-plugin-i18n/kibana.jsonc +++ b/packages/kbn-eslint-plugin-i18n/kibana.jsonc @@ -1,6 +1,6 @@ { "type": "shared-common", "id": "@kbn/eslint-plugin-i18n", - "owner": "@elastic/actionable-observability", + "owner": "@elastic/obs-knowledge-team", "devOnly": true } diff --git a/packages/kbn-eslint-plugin-telemetry/kibana.jsonc b/packages/kbn-eslint-plugin-telemetry/kibana.jsonc index 79c8fbf8adb2bf..e3c245a5275d88 100644 --- a/packages/kbn-eslint-plugin-telemetry/kibana.jsonc +++ b/packages/kbn-eslint-plugin-telemetry/kibana.jsonc @@ -1,6 +1,6 @@ { "type": "shared-common", "id": "@kbn/eslint-plugin-telemetry", - "owner": "@elastic/actionable-observability", + "owner": "@elastic/obs-knowledge-team", "devOnly": true } diff --git a/packages/kbn-i18n/src/core/helper.ts b/packages/kbn-i18n/src/core/helper.ts index 405f0596cd0371..eef4bb88e49277 100644 --- a/packages/kbn-i18n/src/core/helper.ts +++ b/packages/kbn-i18n/src/core/helper.ts @@ -18,17 +18,12 @@ export const unique = (arr: T[] = []): T[] => [...new Set(arr)]; const merge = (a: any, b: any): { [k: string]: any } => unique([...Object.keys(a), ...Object.keys(b)]).reduce((acc, key) => { if (isObject(a[key]) && isObject(b[key]) && !Array.isArray(a[key]) && !Array.isArray(b[key])) { - return { - ...acc, - [key]: merge(a[key], b[key]), - }; + acc[key] = merge(a[key], b[key]); + } else { + acc[key] = b[key] === undefined ? a[key] : b[key]; } - - return { - ...acc, - [key]: b[key] === undefined ? a[key] : b[key], - }; - }, {}); + return acc; + }, {} as { [k: string]: any }); export const mergeAll = (...sources: any[]) => sources.filter(isObject).reduce((acc, source) => merge(acc, source)); diff --git a/packages/kbn-i18n/src/loader.ts b/packages/kbn-i18n/src/loader.ts index 5b4417b54646c3..55c924e989daf0 100644 --- a/packages/kbn-i18n/src/loader.ts +++ b/packages/kbn-i18n/src/loader.ts @@ -148,13 +148,10 @@ export async function getAllTranslations(): Promise<{ [key: string]: Translation const locales = getRegisteredLocales(); const translations = await Promise.all(locales.map(getTranslationsByLocale)); - return locales.reduce( - (acc, locale, index) => ({ - ...acc, - [locale]: translations[index], - }), - {} - ); + return locales.reduce((acc, locale, index) => { + acc[locale] = translations[index]; + return acc; + }, {} as { [key: string]: Translation }); } /** diff --git a/packages/kbn-io-ts-utils/kibana.jsonc b/packages/kbn-io-ts-utils/kibana.jsonc index 7d03717a531538..ea3032a472dc64 100644 --- a/packages/kbn-io-ts-utils/kibana.jsonc +++ b/packages/kbn-io-ts-utils/kibana.jsonc @@ -1,5 +1,5 @@ { "type": "shared-common", "id": "@kbn/io-ts-utils", - "owner": "@elastic/apm-ui" + "owner": "@elastic/obs-knowledge-team" } diff --git a/packages/kbn-lens-embeddable-utils/kibana.jsonc b/packages/kbn-lens-embeddable-utils/kibana.jsonc index d637ea2f24ccbd..9dc67508d99e91 100644 --- a/packages/kbn-lens-embeddable-utils/kibana.jsonc +++ b/packages/kbn-lens-embeddable-utils/kibana.jsonc @@ -1,5 +1,5 @@ { "type": "shared-browser", "id": "@kbn/lens-embeddable-utils", - "owner": "@elastic/infra-monitoring-ui" + "owner": "@elastic/obs-ux-infra_services-team" } diff --git a/packages/kbn-openapi-generator/src/template_service/templates/zod_operation_schema.handlebars b/packages/kbn-openapi-generator/src/template_service/templates/zod_operation_schema.handlebars index 6bb6fccf7d3b32..5395edbcf5f25c 100644 --- a/packages/kbn-openapi-generator/src/template_service/templates/zod_operation_schema.handlebars +++ b/packages/kbn-openapi-generator/src/template_service/templates/zod_operation_schema.handlebars @@ -6,7 +6,7 @@ */ import { z } from "zod"; -import { requiredOptional, isValidDateMath } from "@kbn/zod-helpers" +import { requiredOptional, isValidDateMath, ArrayFromString, BooleanFromString } from "@kbn/zod-helpers" {{> disclaimer}} diff --git a/packages/kbn-openapi-generator/src/template_service/templates/zod_query_item.handlebars b/packages/kbn-openapi-generator/src/template_service/templates/zod_query_item.handlebars index 7fa146cd783e48..ad51f934b7fded 100644 --- a/packages/kbn-openapi-generator/src/template_service/templates/zod_query_item.handlebars +++ b/packages/kbn-openapi-generator/src/template_service/templates/zod_query_item.handlebars @@ -19,10 +19,7 @@ {{~/if~}} {{~#if (eq type "array")}} - z.preprocess( - (value: unknown) => (typeof value === "string") ? value === '' ? [] : value.split(",") : value, - z.array({{~> zod_schema_item items ~}}) - ) + ArrayFromString({{~> zod_schema_item items ~}}) {{~#if minItems}}.min({{minItems}}){{/if~}} {{~#if maxItems}}.max({{maxItems}}){{/if~}} {{~#if (eq requiredBool false)}}.optional(){{/if~}} @@ -30,12 +27,9 @@ {{~/if~}} {{~#if (eq type "boolean")}} - z.preprocess( - (value: unknown) => (typeof value === "boolean") ? String(value) : value, - z.enum(["true", "false"]) - {{~#if (defined default)}}.default("{{{toJSON default}}}"){{/if~}} - .transform((value) => value === "true") - ) + BooleanFromString + {{~#if (eq requiredBool false)}}.optional(){{/if~}} + {{~#if (defined default)}}.default({{{toJSON default}}}){{/if~}} {{~/if~}} {{~#if (eq type "string")}} diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index de07bccf8bbcbe..d7a5d1c9d09f2f 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -28,6 +28,7 @@ pageLoadAssetSize: dashboard: 82025 dashboardEnhanced: 65646 data: 454087 + datasetQuality: 35000 dataViewEditor: 28082 dataViewFieldEditor: 27000 dataViewManagement: 5100 @@ -88,7 +89,7 @@ pageLoadAssetSize: licensing: 29004 links: 44490 lists: 22900 - logExplorer: 39045 + logExplorer: 54342 logsShared: 281060 logstash: 53548 management: 46112 diff --git a/packages/kbn-profiling-utils/kibana.jsonc b/packages/kbn-profiling-utils/kibana.jsonc index dc45e822e620b0..d41a4db71299d7 100644 --- a/packages/kbn-profiling-utils/kibana.jsonc +++ b/packages/kbn-profiling-utils/kibana.jsonc @@ -1,5 +1,5 @@ { "type": "shared-common", "id": "@kbn/profiling-utils", - "owner": "@elastic/profiling-ui" + "owner": "@elastic/obs-ux-infra_services-team" } diff --git a/packages/kbn-resizable-layout/src/panels_resizable.test.tsx b/packages/kbn-resizable-layout/src/panels_resizable.test.tsx index 3ea2ccc87aaebb..f3ebde2aa73edf 100644 --- a/packages/kbn-resizable-layout/src/panels_resizable.test.tsx +++ b/packages/kbn-resizable-layout/src/panels_resizable.test.tsx @@ -24,7 +24,7 @@ jest.mock('@elastic/eui', () => ({ })); import * as eui from '@elastic/eui'; -import { waitFor } from '@testing-library/dom'; +import { waitFor } from '@testing-library/react'; import { ResizableLayoutDirection } from '../types'; describe('Panels resizable', () => { diff --git a/packages/kbn-rule-data-utils/kibana.jsonc b/packages/kbn-rule-data-utils/kibana.jsonc index 6650a9b1d67f07..4d9d77fbeeb722 100644 --- a/packages/kbn-rule-data-utils/kibana.jsonc +++ b/packages/kbn-rule-data-utils/kibana.jsonc @@ -3,7 +3,7 @@ "id": "@kbn/rule-data-utils", "owner": [ "@elastic/security-detections-response", - "@elastic/actionable-observability", - "@elastic/response-ops" + "@elastic/response-ops", + "@elastic/obs-ux-management-team" ] } diff --git a/packages/kbn-rule-data-utils/src/alerts_as_data_rbac.ts b/packages/kbn-rule-data-utils/src/alerts_as_data_rbac.ts index 06fe06c63b4b9d..0427570b1cbc6a 100644 --- a/packages/kbn-rule-data-utils/src/alerts_as_data_rbac.ts +++ b/packages/kbn-rule-data-utils/src/alerts_as_data_rbac.ts @@ -23,6 +23,7 @@ export const AlertConsumers = { SLO: 'slo', SIEM: 'siem', UPTIME: 'uptime', + ML: 'ml', } as const; export type AlertConsumers = typeof AlertConsumers[keyof typeof AlertConsumers]; export type STATUS_VALUES = 'open' | 'acknowledged' | 'closed' | 'in-progress'; // TODO: remove 'in-progress' after migration to 'acknowledged' diff --git a/packages/kbn-rule-data-utils/src/default_alerts_as_data.ts b/packages/kbn-rule-data-utils/src/default_alerts_as_data.ts index 3b2ea148591dc9..d1aec24a9b26eb 100644 --- a/packages/kbn-rule-data-utils/src/default_alerts_as_data.ts +++ b/packages/kbn-rule-data-utils/src/default_alerts_as_data.ts @@ -27,7 +27,7 @@ const ALERT_ACTION_GROUP = `${ALERT_NAMESPACE}.action_group` as const; // kibana.alert.case_ids - array of cases associated with the alert const ALERT_CASE_IDS = `${ALERT_NAMESPACE}.case_ids` as const; -// kibana.alert.duration.us - alert duration in nanoseconds - updated each execution +// kibana.alert.duration.us - alert duration in microseconds - updated each execution // that the alert is active const ALERT_DURATION = `${ALERT_NAMESPACE}.duration.us` as const; diff --git a/packages/kbn-rule-data-utils/src/rule_types/o11y_rules.ts b/packages/kbn-rule-data-utils/src/rule_types/o11y_rules.ts index b88e0f708a9801..844c8e994947c7 100644 --- a/packages/kbn-rule-data-utils/src/rule_types/o11y_rules.ts +++ b/packages/kbn-rule-data-utils/src/rule_types/o11y_rules.ts @@ -8,6 +8,8 @@ export const OBSERVABILITY_THRESHOLD_RULE_TYPE_ID = 'observability.rules.custom_threshold'; +export const METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID = 'metrics.alert.inventory.threshold'; + export enum ApmRuleType { ErrorCount = 'apm.error_rate', // ErrorRate was renamed to ErrorCount but the key is kept as `error_rate` for backwards-compat. TransactionErrorRate = 'apm.transaction_error_rate', diff --git a/packages/kbn-search-api-panels/components/integrations_panel.tsx b/packages/kbn-search-api-panels/components/integrations_panel.tsx index dc4c4724775785..3f6005f26cebd6 100644 --- a/packages/kbn-search-api-panels/components/integrations_panel.tsx +++ b/packages/kbn-search-api-panels/components/integrations_panel.tsx @@ -127,7 +127,7 @@ export const IntegrationsPanel: React.FC = ({

{i18n.translate('searchApiPanels.welcomeBanner.ingestData.connectorsTitle', { - defaultMessage: 'Connector Client', + defaultMessage: 'Connector clients', })}

@@ -135,7 +135,7 @@ export const IntegrationsPanel: React.FC = ({ {i18n.translate('searchApiPanels.welcomeBanner.ingestData.connectorsDescription', { defaultMessage: - 'Specialized integrations for syncing data from third-party sources to Elasticsearch. Use Elastic Connectors to sync content from a range of databases and object stores.', + 'Specialized integrations for syncing data from third-party sources to Elasticsearch. Use Elastic connectors to sync content from a range of databases and object stores.', })} @@ -153,7 +153,7 @@ export const IntegrationsPanel: React.FC = ({ label={i18n.translate( 'searchApiPanels.welcomeBanner.ingestData.connectorsPythonLink', { - defaultMessage: 'connectors-python', + defaultMessage: 'elastic/connectors', } )} assetBasePath={assetBasePath} diff --git a/packages/kbn-search-api-panels/components/try_in_console_button.tsx b/packages/kbn-search-api-panels/components/try_in_console_button.tsx index 93012c58a036d7..fe109e025e2e5a 100644 --- a/packages/kbn-search-api-panels/components/try_in_console_button.tsx +++ b/packages/kbn-search-api-panels/components/try_in_console_button.tsx @@ -43,7 +43,7 @@ export const TryInConsoleButton = ({ ); diff --git a/packages/kbn-securitysolution-exception-list-components/src/list_header/use_list_header.test.ts b/packages/kbn-securitysolution-exception-list-components/src/list_header/use_list_header.test.ts index 9ddd782e132cdf..867ad6ce8e3e61 100644 --- a/packages/kbn-securitysolution-exception-list-components/src/list_header/use_list_header.test.ts +++ b/packages/kbn-securitysolution-exception-list-components/src/list_header/use_list_header.test.ts @@ -5,7 +5,7 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import { waitFor } from '@testing-library/dom'; +import { waitFor } from '@testing-library/react'; import { act, renderHook } from '@testing-library/react-hooks'; import { useExceptionListHeader } from './use_list_header'; diff --git a/packages/kbn-server-route-repository/kibana.jsonc b/packages/kbn-server-route-repository/kibana.jsonc index 8161be4b809514..dbf7fc396428c3 100644 --- a/packages/kbn-server-route-repository/kibana.jsonc +++ b/packages/kbn-server-route-repository/kibana.jsonc @@ -1,5 +1,5 @@ { "type": "shared-common", "id": "@kbn/server-route-repository", - "owner": "@elastic/apm-ui" + "owner": ["@elastic/obs-knowledge-team", "@elastic/obs-ux-management-team"] } diff --git a/packages/kbn-shared-svg/kibana.jsonc b/packages/kbn-shared-svg/kibana.jsonc index 6634bd9dffc5df..89949f5b7d276e 100644 --- a/packages/kbn-shared-svg/kibana.jsonc +++ b/packages/kbn-shared-svg/kibana.jsonc @@ -1,5 +1,5 @@ { "type": "shared-common", "id": "@kbn/shared-svg", - "owner": "@elastic/apm-ui" + "owner": "@elastic/obs-ux-infra_services-team" } diff --git a/packages/kbn-std/src/iteration/map.ts b/packages/kbn-std/src/iteration/map.ts index 44765d69e1bd0d..a65951cc14db5d 100644 --- a/packages/kbn-std/src/iteration/map.ts +++ b/packages/kbn-std/src/iteration/map.ts @@ -58,5 +58,8 @@ export async function asyncMapWithLimit( return results .sort(([a], [b]) => a - b) - .reduce((acc: T2[], [, result]) => acc.concat(result), []); + .reduce((acc: T2[], [, result]) => { + acc.push(...result); + return acc; + }, []); } diff --git a/packages/kbn-telemetry-tools/src/tools/serializer.ts b/packages/kbn-telemetry-tools/src/tools/serializer.ts index a33d2d78e4137a..ab891f12203233 100644 --- a/packages/kbn-telemetry-tools/src/tools/serializer.ts +++ b/packages/kbn-telemetry-tools/src/tools/serializer.ts @@ -178,7 +178,10 @@ export function getDescriptor(node: ts.Node, program: ts.Program): Descriptor | const constraints = getConstraints(constraint, program); const constraintsArray = Array.isArray(constraints) ? constraints : [constraints]; if (typeof constraintsArray[0] === 'string') { - return constraintsArray.reduce((acc, c) => ({ ...acc, [c]: descriptor }), {}); + return constraintsArray.reduce((acc, c) => { + (acc as Record)[c] = descriptor; + return acc; + }, {}); } } return { '@@INDEX@@': descriptor }; diff --git a/packages/kbn-typed-react-router-config/kibana.jsonc b/packages/kbn-typed-react-router-config/kibana.jsonc index f508346f8c26e0..0462d28238890b 100644 --- a/packages/kbn-typed-react-router-config/kibana.jsonc +++ b/packages/kbn-typed-react-router-config/kibana.jsonc @@ -1,5 +1,5 @@ { "type": "shared-common", "id": "@kbn/typed-react-router-config", - "owner": "@elastic/apm-ui" + "owner": ["@elastic/obs-knowledge-team", "@elastic/obs-ux-management-team"] } diff --git a/packages/kbn-unified-data-table/src/utils/copy_value_to_clipboard.test.tsx b/packages/kbn-unified-data-table/src/utils/copy_value_to_clipboard.test.tsx index 7ff5c9b3f19b6a..f49a7ef95ed6db 100644 --- a/packages/kbn-unified-data-table/src/utils/copy_value_to_clipboard.test.tsx +++ b/packages/kbn-unified-data-table/src/utils/copy_value_to_clipboard.test.tsx @@ -93,7 +93,7 @@ describe('copyValueToClipboard', () => { columnDisplayName: 'text_message', }); - expect(result).toBe('"text_message"'); + expect(result).toBe('text_message'); expect(execCommandMock).toHaveBeenCalledWith('copy'); expect(servicesMock.toastNotifications.addInfo).toHaveBeenCalledWith({ title: 'Copied to clipboard', diff --git a/packages/kbn-unified-data-table/src/utils/copy_value_to_clipboard.ts b/packages/kbn-unified-data-table/src/utils/copy_value_to_clipboard.ts index 2e9620b42728b4..2fc0606624a0b6 100644 --- a/packages/kbn-unified-data-table/src/utils/copy_value_to_clipboard.ts +++ b/packages/kbn-unified-data-table/src/utils/copy_value_to_clipboard.ts @@ -131,9 +131,7 @@ export const copyColumnNameToClipboard = ({ columnDisplayName: string; toastNotifications: ToastsStart; }): string | null => { - const nameFormattedResult = convertNameToString(columnDisplayName); - const textToCopy = nameFormattedResult.formattedString; - const copied = copyToClipboard(textToCopy); + const copied = copyToClipboard(columnDisplayName); if (!copied) { toastNotifications.addWarning({ @@ -147,16 +145,9 @@ export const copyColumnNameToClipboard = ({ defaultMessage: 'Copied to clipboard', }); - if (nameFormattedResult.withFormula) { - toastNotifications.addWarning({ - title: toastTitle, - text: WARNING_FOR_FORMULAS, - }); - } else { - toastNotifications.addInfo({ - title: toastTitle, - }); - } + toastNotifications.addInfo({ + title: toastTitle, + }); - return textToCopy; + return columnDisplayName; }; diff --git a/packages/kbn-use-tracked-promise/kibana.jsonc b/packages/kbn-use-tracked-promise/kibana.jsonc index a7b90045c462aa..959dda0d05a66d 100644 --- a/packages/kbn-use-tracked-promise/kibana.jsonc +++ b/packages/kbn-use-tracked-promise/kibana.jsonc @@ -1,5 +1,5 @@ { "type": "shared-common", "id": "@kbn/use-tracked-promise", - "owner": "@elastic/infra-monitoring-ui" + "owner": "@elastic/obs-ux-logs-team" } diff --git a/packages/kbn-xstate-utils/kibana.jsonc b/packages/kbn-xstate-utils/kibana.jsonc index 086bce23401aa6..cd1151a3f21034 100644 --- a/packages/kbn-xstate-utils/kibana.jsonc +++ b/packages/kbn-xstate-utils/kibana.jsonc @@ -1,5 +1,5 @@ { "type": "shared-common", "id": "@kbn/xstate-utils", - "owner": "@elastic/infra-monitoring-ui" + "owner": "@elastic/obs-ux-logs-team" } diff --git a/packages/kbn-zod-helpers/index.ts b/packages/kbn-zod-helpers/index.ts index f1062064dc5cfa..d8a62f58686b2d 100644 --- a/packages/kbn-zod-helpers/index.ts +++ b/packages/kbn-zod-helpers/index.ts @@ -6,8 +6,11 @@ * Side Public License, v 1. */ +export * from './src/array_from_string'; +export * from './src/boolean_from_string'; export * from './src/expect_parse_error'; export * from './src/expect_parse_success'; export * from './src/is_valid_date_math'; export * from './src/required_optional'; +export * from './src/safe_parse_result'; export * from './src/stringify_zod_error'; diff --git a/packages/kbn-zod-helpers/src/array_from_string.test.ts b/packages/kbn-zod-helpers/src/array_from_string.test.ts new file mode 100644 index 00000000000000..ba27fddb0c9b50 --- /dev/null +++ b/packages/kbn-zod-helpers/src/array_from_string.test.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ArrayFromString } from './array_from_string'; +import * as z from 'zod'; + +describe('ArrayFromString', () => { + const itemsSchema = z.string(); + + it('should return an array when input is a string', () => { + const result = ArrayFromString(itemsSchema).parse('a,b,c'); + expect(result).toEqual(['a', 'b', 'c']); + }); + + it('should return an empty array when input is an empty string', () => { + const result = ArrayFromString(itemsSchema).parse(''); + expect(result).toEqual([]); + }); + + it('should return the input as is when it is not a string', () => { + const input = ['a', 'b', 'c']; + const result = ArrayFromString(itemsSchema).parse(input); + expect(result).toEqual(input); + }); + + it('should throw an error when input is not a string or an array', () => { + expect(() => ArrayFromString(itemsSchema).parse(123)).toThrow(); + }); +}); diff --git a/packages/kbn-zod-helpers/src/array_from_string.ts b/packages/kbn-zod-helpers/src/array_from_string.ts new file mode 100644 index 00000000000000..24247e2d14c401 --- /dev/null +++ b/packages/kbn-zod-helpers/src/array_from_string.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import * as z from 'zod'; + +/** + * This is a helper schema to convert comma separated strings to arrays. Useful + * for processing query params. + * + * @param schema Array items schema + * @returns Array schema that accepts a comma-separated string as input + */ +export function ArrayFromString(schema: T) { + return z.preprocess( + (value: unknown) => + typeof value === 'string' ? (value === '' ? [] : value.split(',')) : value, + z.array(schema) + ); +} diff --git a/packages/kbn-zod-helpers/src/boolean_from_string.test.ts b/packages/kbn-zod-helpers/src/boolean_from_string.test.ts new file mode 100644 index 00000000000000..842eda2d6e9a2e --- /dev/null +++ b/packages/kbn-zod-helpers/src/boolean_from_string.test.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { BooleanFromString } from './boolean_from_string'; + +describe('BooleanFromString', () => { + it('should return true when input is "true"', () => { + expect(BooleanFromString.parse('true')).toBe(true); + }); + + it('should return false when input is "false"', () => { + expect(BooleanFromString.parse('false')).toBe(false); + }); + + it('should return true when input is true', () => { + expect(BooleanFromString.parse(true)).toBe(true); + }); + + it('should return false when input is false', () => { + expect(BooleanFromString.parse(false)).toBe(false); + }); + + it('should throw an error when input is not a boolean or "true" or "false"', () => { + expect(() => BooleanFromString.parse('not a boolean')).toThrow(); + expect(() => BooleanFromString.parse(42)).toThrow(); + }); +}); diff --git a/packages/kbn-zod-helpers/src/boolean_from_string.ts b/packages/kbn-zod-helpers/src/boolean_from_string.ts new file mode 100644 index 00000000000000..d73e77ea1bddcf --- /dev/null +++ b/packages/kbn-zod-helpers/src/boolean_from_string.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import * as z from 'zod'; + +/** + * This is a helper schema to convert a boolean string ("true" or "false") to a + * boolean. Useful for processing query params. + * + * Accepts "true" or "false" as strings, or a boolean. + */ +export const BooleanFromString = z + .enum(['true', 'false']) + .or(z.boolean()) + .transform((value) => { + if (typeof value === 'boolean') { + return value; + } + return value === 'true'; + }); diff --git a/packages/kbn-zod-helpers/src/expect_parse_success.ts b/packages/kbn-zod-helpers/src/expect_parse_success.ts index 4fc4a74047933f..8c9e518c27b87d 100644 --- a/packages/kbn-zod-helpers/src/expect_parse_success.ts +++ b/packages/kbn-zod-helpers/src/expect_parse_success.ts @@ -7,9 +7,14 @@ */ import type { SafeParseReturnType, SafeParseSuccess } from 'zod'; +import { stringifyZodError } from './stringify_zod_error'; export function expectParseSuccess( result: SafeParseReturnType ): asserts result is SafeParseSuccess { - expect(result.success).toEqual(true); + if (!result.success) { + // We are throwing here instead of using assertions because we want to show + // the stringified error to assist with debugging. + throw new Error(`Expected parse success, got error: ${stringifyZodError(result.error)}`); + } } diff --git a/packages/kbn-zod-helpers/src/safe_parse_result.ts b/packages/kbn-zod-helpers/src/safe_parse_result.ts new file mode 100644 index 00000000000000..4e9b701a18fafa --- /dev/null +++ b/packages/kbn-zod-helpers/src/safe_parse_result.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import * as z from 'zod'; + +/** + * Safely parse a payload against a schema, returning the output or undefined. + * This method does not throw validation errors and is useful for validating + * optional objects when we don't care about errors. + * + * @param payload Schema payload + * @param schema Validation schema + * @returns Schema output or undefined + */ +export function safeParseResult( + payload: unknown, + schema: T +): T['_output'] | undefined { + const result = schema.safeParse(payload); + if (result.success) { + return result.data; + } +} diff --git a/packages/kbn-zod-helpers/src/stringify_zod_error.ts b/packages/kbn-zod-helpers/src/stringify_zod_error.ts index b873870f99381c..1fbaec8bbac857 100644 --- a/packages/kbn-zod-helpers/src/stringify_zod_error.ts +++ b/packages/kbn-zod-helpers/src/stringify_zod_error.ts @@ -6,16 +6,41 @@ * Side Public License, v 1. */ -import { ZodError } from 'zod'; +import { ZodError, ZodIssue } from 'zod'; + +const MAX_ERRORS = 5; export function stringifyZodError(err: ZodError) { - return err.issues - .map((issue) => { - // If the path is empty, the error is for the root object - if (issue.path.length === 0) { - return issue.message; - } - return `${issue.path.join('.')}: ${issue.message}`; - }) - .join(', '); + const errorMessages: string[] = []; + + const issues = err.issues; + + // Recursively traverse all issues + while (issues.length > 0) { + const issue = issues.shift()!; + + // If the issue is an invalid union, we need to traverse all issues in the + // "unionErrors" array + if (issue.code === 'invalid_union') { + issues.push(...issue.unionErrors.flatMap((e) => e.issues)); + continue; + } + + errorMessages.push(stringifyIssue(issue)); + } + + const extraErrorCount = errorMessages.length - MAX_ERRORS; + if (extraErrorCount > 0) { + errorMessages.splice(MAX_ERRORS); + errorMessages.push(`and ${extraErrorCount} more`); + } + + return errorMessages.join(', '); +} + +function stringifyIssue(issue: ZodIssue) { + if (issue.path.length === 0) { + return issue.message; + } + return `${issue.path.join('.')}: ${issue.message}`; } diff --git a/packages/serverless/settings/observability_project/kibana.jsonc b/packages/serverless/settings/observability_project/kibana.jsonc index 4df29091e66198..e2db00d894bcf6 100644 --- a/packages/serverless/settings/observability_project/kibana.jsonc +++ b/packages/serverless/settings/observability_project/kibana.jsonc @@ -1,5 +1,5 @@ { "type": "shared-common", "id": "@kbn/serverless-observability-settings", - "owner": "@elastic/appex-sharedux @elastic/apm-ui @elastic/platform-deployment-management" + "owner": "@elastic/appex-sharedux @elastic/platform-deployment-management @elastic/obs-ux-management-team" } diff --git a/packages/shared-ux/error_boundary/src/ui/error_boundary.test.tsx b/packages/shared-ux/error_boundary/src/ui/error_boundary.test.tsx index ed5d4a60534c0c..05a69a7ccab9ed 100644 --- a/packages/shared-ux/error_boundary/src/ui/error_boundary.test.tsx +++ b/packages/shared-ux/error_boundary/src/ui/error_boundary.test.tsx @@ -49,7 +49,7 @@ describe('', () => { expect(await findByText(strings.recoverable.callout.title())).toBeVisible(); expect(await findByText(strings.recoverable.callout.pageReloadButton())).toBeVisible(); - (await findByTestId('recoverablePromptReloadBtn')).click(); + (await findByTestId('errorBoundaryRecoverablePromptReloadBtn')).click(); expect(reloadSpy).toHaveBeenCalledTimes(1); }); @@ -69,7 +69,7 @@ describe('', () => { expect(await findByText(strings.fatal.callout.showDetailsButton())).toBeVisible(); expect(await findByText(strings.fatal.callout.pageReloadButton())).toBeVisible(); - (await findByTestId('fatalPromptReloadBtn')).click(); + (await findByTestId('errorBoundaryFatalPromptReloadBtn')).click(); expect(reloadSpy).toHaveBeenCalledTimes(1); }); diff --git a/packages/shared-ux/error_boundary/src/ui/message_components.tsx b/packages/shared-ux/error_boundary/src/ui/message_components.tsx index 6e481cb5d8216d..1e44fa68141bc3 100644 --- a/packages/shared-ux/error_boundary/src/ui/message_components.tsx +++ b/packages/shared-ux/error_boundary/src/ui/message_components.tsx @@ -55,7 +55,7 @@ const CodePanel: React.FC void }> = (props) - +

{(error.stack ?? error.toString()) + '\n\n'}

{errorName} @@ -93,25 +93,29 @@ export const FatalPrompt: React.FC = (props) => { return ( {strings.fatal.callout.title()}} + title={

{strings.fatal.callout.title()}

} color="danger" iconType="error" body={ <> -

{strings.fatal.callout.body()}

+

{strings.fatal.callout.body()}

{strings.fatal.callout.pageReloadButton()}

- setIsFlyoutVisible(true)}> + setIsFlyoutVisible(true)} + data-test-subj="errorBoundaryFatalShowDetailsBtn" + > {strings.fatal.callout.showDetailsButton()} {isFlyoutVisible ? ( @@ -128,17 +132,25 @@ export const RecoverablePrompt = (props: ErrorCalloutProps) => { const { onClickRefresh } = props; return ( {strings.recoverable.callout.title()}} - body={

{strings.recoverable.callout.body()}

} + title={ +

+ {strings.recoverable.callout.title()} +

+ } color="warning" + iconType="warning" + body={ +

+ {strings.recoverable.callout.body()} +

+ } actions={ {strings.recoverable.callout.pageReloadButton()} diff --git a/src/cli/dist.js b/src/cli/dist.js index 9bd7696a44561b..5551c4e5c7774d 100644 --- a/src/cli/dist.js +++ b/src/cli/dist.js @@ -9,4 +9,5 @@ require('../setup_node_env/dist'); require('./apm')(); require('../setup_node_env/root'); +require('../setup_node_env/mute_libraries'); require('./cli'); diff --git a/src/core/server/integration_tests/capabilities/capabilities_service.test.ts b/src/core/server/integration_tests/capabilities/capabilities_service.test.ts index 23a8905bcb565d..6ee87dc5a79313 100644 --- a/src/core/server/integration_tests/capabilities/capabilities_service.test.ts +++ b/src/core/server/integration_tests/capabilities/capabilities_service.test.ts @@ -82,7 +82,7 @@ describe('CapabilitiesService', () => { serviceSetup.registerProvider(() => getInitialCapabilities()); const switcher = jest.fn((_, capabilities) => capabilities); - serviceSetup.registerSwitcher(switcher); + serviceSetup.registerSwitcher(switcher, { capabilityPath: '*' }); const result = await supertest(httpSetup.server.listener) .post('/api/core/capabilities') @@ -113,7 +113,7 @@ describe('CapabilitiesService', () => { serviceSetup.registerProvider(() => getInitialCapabilities()); const switcher = jest.fn((_, capabilities) => capabilities); - serviceSetup.registerSwitcher(switcher); + serviceSetup.registerSwitcher(switcher, { capabilityPath: '*' }); const result = await supertest(httpSetup.server.listener) .post('/api/core/capabilities?useDefaultCapabilities=true') diff --git a/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts b/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts index 66fbd33d1568a6..dd736740a9377d 100644 --- a/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts +++ b/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts @@ -107,7 +107,7 @@ describe('checking migration metadata changes on all registered SO types', () => "ingest-agent-policies": "7633e578f60c074f8267bc50ec4763845e431437", "ingest-download-sources": "279a68147e62e4d8858c09ad1cf03bd5551ce58d", "ingest-outputs": "8546f1123ec30dcbd6f238f72729c5f1656a4d9b", - "ingest-package-policies": "a0c9fb48e04dcd638e593db55f1c6451523f90ea", + "ingest-package-policies": "f4c2767e852b700a8b82678925b86bac08958b43", "ingest_manager_settings": "64955ef1b7a9ffa894d4bb9cf863b5602bfa6885", "inventory-view": "b8683c8e352a286b4aca1ab21003115a4800af83", "kql-telemetry": "93c1d16c1a0dfca9c8842062cf5ef8f62ae401ad", diff --git a/src/core/server/integration_tests/elasticsearch/errors.test.ts b/src/core/server/integration_tests/elasticsearch/errors.test.ts new file mode 100644 index 00000000000000..b9dcfb33a23a48 --- /dev/null +++ b/src/core/server/integration_tests/elasticsearch/errors.test.ts @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { + createTestServers, + type TestElasticsearchUtils, + type TestKibanaUtils, +} from '@kbn/core-test-helpers-kbn-server'; + +describe('elasticsearch clients errors', () => { + let esServer: TestElasticsearchUtils; + let kibanaServer: TestKibanaUtils; + + beforeAll(async () => { + const { startES, startKibana } = createTestServers({ + adjustTimeout: jest.setTimeout, + }); + + esServer = await startES(); + kibanaServer = await startKibana(); + }); + + afterAll(async () => { + await kibanaServer.stop(); + await esServer.stop(); + }); + + it('has the proper JSON representation', async () => { + const esClient = kibanaServer.coreStart.elasticsearch.client.asInternalUser; + + try { + await esClient.search({ + index: '.kibana', + // @ts-expect-error yes this is invalid + query: { someInvalidQuery: { foo: 'bar' } }, + }); + expect('should have thrown').toEqual('but it did not'); + } catch (e) { + expect(JSON.stringify(e)).toMatchInlineSnapshot( + `"{\\"name\\":\\"ResponseError\\",\\"message\\":\\"parsing_exception\\\\n\\\\tCaused by:\\\\n\\\\t\\\\tnamed_object_not_found_exception: [1:30] unknown field [someInvalidQuery]\\\\n\\\\tRoot causes:\\\\n\\\\t\\\\tparsing_exception: unknown query [someInvalidQuery]\\"}"` + ); + } + }); + + it('has the proper string representation', async () => { + const esClient = kibanaServer.coreStart.elasticsearch.client.asInternalUser; + + try { + await esClient.search({ + index: '.kibana', + // @ts-expect-error yes this is invalid + query: { someInvalidQuery: { foo: 'bar' } }, + }); + expect('should have thrown').toEqual('but it did not'); + } catch (e) { + expect(String(e)).toMatchInlineSnapshot(` + "ResponseError: parsing_exception + Caused by: + named_object_not_found_exception: [1:30] unknown field [someInvalidQuery] + Root causes: + parsing_exception: unknown query [someInvalidQuery]" + `); + } + }); +}); diff --git a/src/dev/build/tasks/os_packages/docker_generator/templates/base/Dockerfile b/src/dev/build/tasks/os_packages/docker_generator/templates/base/Dockerfile index 8db4b419a184af..25f4345ffbcc31 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/templates/base/Dockerfile +++ b/src/dev/build/tasks/os_packages/docker_generator/templates/base/Dockerfile @@ -128,6 +128,7 @@ COPY --chown=1000:0 config/serverless.oblt.yml /usr/share/kibana/config/serverle COPY --chown=1000:0 config/serverless.security.yml /usr/share/kibana/config/serverless.security.yml # Supportability enhancement: enable capturing heap snapshots. See https://nodejs.org/api/cli.html#--heapsnapshot-signalsignal RUN echo '\n--heapsnapshot-signal=SIGUSR2' >> config/node.options +RUN echo '--diagnostic-dir=./data' >> config/node.options {{/serverless}} {{^opensslLegacyProvider}} RUN sed 's/\(--openssl-legacy-provider\)/#\1/' -i config/node.options diff --git a/src/dev/build/tasks/os_packages/docker_generator/templates/ironbank/Dockerfile b/src/dev/build/tasks/os_packages/docker_generator/templates/ironbank/Dockerfile index b68130f1d201c3..7fbba7d72ea786 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/templates/ironbank/Dockerfile +++ b/src/dev/build/tasks/os_packages/docker_generator/templates/ironbank/Dockerfile @@ -4,7 +4,7 @@ ################################################################################ ARG BASE_REGISTRY=registry1.dso.mil ARG BASE_IMAGE=redhat/ubi/ubi9 -ARG BASE_TAG=9.2 +ARG BASE_TAG=9.3 FROM ${BASE_REGISTRY}/${BASE_IMAGE}:${BASE_TAG} as prep_files diff --git a/src/dev/build/tasks/os_packages/docker_generator/templates/ironbank/hardening_manifest.yaml b/src/dev/build/tasks/os_packages/docker_generator/templates/ironbank/hardening_manifest.yaml index 3bbd4d7f31d122..43800a045da9c6 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/templates/ironbank/hardening_manifest.yaml +++ b/src/dev/build/tasks/os_packages/docker_generator/templates/ironbank/hardening_manifest.yaml @@ -14,7 +14,7 @@ tags: # Build args passed to Dockerfile ARGs args: BASE_IMAGE: 'redhat/ubi/ubi9' - BASE_TAG: '9.2' + BASE_TAG: '9.3' # Docker image labels labels: diff --git a/src/dev/license_checker/config.ts b/src/dev/license_checker/config.ts index 3646fe3ab65def..1680edb446e36f 100644 --- a/src/dev/license_checker/config.ts +++ b/src/dev/license_checker/config.ts @@ -84,7 +84,7 @@ export const PER_PACKAGE_ALLOWED_LICENSES = { export const LICENSE_OVERRIDES = { 'jsts@1.6.2': ['Eclipse Distribution License - v 1.0'], // cf. https://github.com/bjornharrtell/jsts '@mapbox/jsonlint-lines-primitives@2.0.2': ['MIT'], // license in readme https://github.com/tmcw/jsonlint - '@elastic/ems-client@8.5.0': ['Elastic License 2.0'], + '@elastic/ems-client@8.5.1': ['Elastic License 2.0'], '@elastic/eui@90.0.0': ['SSPL-1.0 OR Elastic License 2.0'], 'language-subtag-registry@0.3.21': ['CC-BY-4.0'], // retired ODC‑By license https://github.com/mattcg/language-subtag-registry 'buffers@0.1.1': ['MIT'], // license in importing module https://www.npmjs.com/package/binary diff --git a/src/plugins/advanced_settings/public/management_app/components/form/form.tsx b/src/plugins/advanced_settings/public/management_app/components/form/form.tsx index e5af8e871cf52d..e45af3c98cce63 100644 --- a/src/plugins/advanced_settings/public/management_app/components/form/form.tsx +++ b/src/plugins/advanced_settings/public/management_app/components/form/form.tsx @@ -191,6 +191,7 @@ export class Form extends PureComponent { title: i18n.translate('advancedSettings.form.requiresPageReloadToastDescription', { defaultMessage: 'One or more settings require you to reload the page to take effect.', }), + toastLifeTimeMs: 15000, text: toMountPoint( diff --git a/src/plugins/dashboard/public/dashboard_app/_dashboard_app_strings.ts b/src/plugins/dashboard/public/dashboard_app/_dashboard_app_strings.ts index 6fefffcc1d668d..8e2f7b07d195a1 100644 --- a/src/plugins/dashboard/public/dashboard_app/_dashboard_app_strings.ts +++ b/src/plugins/dashboard/public/dashboard_app/_dashboard_app_strings.ts @@ -152,7 +152,7 @@ export const shareModalStrings = { */ export const getDashboardBreadcrumb = () => i18n.translate('dashboard.dashboardAppBreadcrumbsTitle', { - defaultMessage: 'Dashboard', + defaultMessage: 'Dashboards', }); export const topNavStrings = { diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx index 4d46b837da5ca6..98af5088967f2d 100644 --- a/src/plugins/dashboard/public/plugin.tsx +++ b/src/plugins/dashboard/public/plugin.tsx @@ -229,7 +229,7 @@ export class DashboardPlugin const app: App = { id: DASHBOARD_APP_ID, - title: 'Dashboard', + title: 'Dashboards', order: 2500, euiIconType: 'logoKibana', defaultPath: `#${LANDING_PAGE_PATH}`, diff --git a/src/plugins/data/common/search/strategies/es_search/types.ts b/src/plugins/data/common/search/strategies/es_search/types.ts index 73bf7961fea9b8..f8c3b73d995a9f 100644 --- a/src/plugins/data/common/search/strategies/es_search/types.ts +++ b/src/plugins/data/common/search/strategies/es_search/types.ts @@ -15,7 +15,8 @@ export type ISearchRequestParams = { trackTotalHits?: boolean; } & estypes.SearchRequest; -export interface IEsSearchRequest extends IKibanaSearchRequest { +export interface IEsSearchRequest + extends IKibanaSearchRequest { indexType?: string; } diff --git a/src/plugins/data/server/search/routes/bsearch.ts b/src/plugins/data/server/search/routes/bsearch.ts index 8b65c5d8eb1dc3..ee19c8cba30a37 100644 --- a/src/plugins/data/server/search/routes/bsearch.ts +++ b/src/plugins/data/server/search/routes/bsearch.ts @@ -11,6 +11,7 @@ import { catchError } from 'rxjs/operators'; import { BfetchServerSetup } from '@kbn/bfetch-plugin/server'; import type { ExecutionContextSetup } from '@kbn/core/server'; import apm from 'elastic-apm-node'; +import { getRequestAbortedSignal } from '../..'; import { IKibanaSearchRequest, IKibanaSearchResponse, @@ -28,6 +29,7 @@ export function registerBsearchRoute( IKibanaSearchResponse >('/internal/bsearch', (request) => { const search = getScoped(request); + const abortSignal = getRequestAbortedSignal(request.events.aborted$); return { /** * @param requestOptions @@ -39,7 +41,7 @@ export function registerBsearchRoute( apm.addLabels(executionContextService.getAsLabels()); return firstValueFrom( - search.search(requestData, restOptions).pipe( + search.search(requestData, { ...restOptions, abortSignal }).pipe( catchError((err) => { // Re-throw as object, to get attributes passed to the client // eslint-disable-next-line no-throw-literal diff --git a/src/plugins/data/server/search/search_service.ts b/src/plugins/data/server/search/search_service.ts index 71a335ce515926..188f853e6a2ce2 100644 --- a/src/plugins/data/server/search/search_service.ts +++ b/src/plugins/data/server/search/search_service.ts @@ -437,11 +437,7 @@ export class SearchService implements Plugin { } }; - private cancel = async ( - deps: SearchStrategyDependencies, - id: string, - options: ISearchOptions = {} - ) => { + private cancel = (deps: SearchStrategyDependencies, id: string, options: ISearchOptions = {}) => { const strategy = this.getSearchStrategy(options.strategy); if (!strategy.cancel) { throw new KbnServerError( @@ -468,14 +464,18 @@ export class SearchService implements Plugin { private cancelSessionSearches = async (deps: SearchStrategyDependencies, sessionId: string) => { const searchIdMapping = await deps.searchSessionsClient.getSearchIdMapping(sessionId); await Promise.allSettled( - Array.from(searchIdMapping).map(([searchId, strategyName]) => { + Array.from(searchIdMapping).map(async ([searchId, strategyName]) => { const searchOptions = { sessionId, strategy: strategyName, isStored: true, }; - return this.cancel(deps, searchId, searchOptions); + try { + await this.cancel(deps, searchId, searchOptions); + } catch (e) { + this.logger.error(`cancelSessionSearches error: ${e.message}`); + } }) ); }; diff --git a/src/plugins/data/server/search/strategies/eql_search/eql_search_strategy.test.ts b/src/plugins/data/server/search/strategies/eql_search/eql_search_strategy.test.ts index 6d61f62cc79abb..475c43a5daed68 100644 --- a/src/plugins/data/server/search/strategies/eql_search/eql_search_strategy.test.ts +++ b/src/plugins/data/server/search/strategies/eql_search/eql_search_strategy.test.ts @@ -15,6 +15,7 @@ import { getMockSearchConfig } from '../../../../config.mock'; const getMockEqlResponse = () => ({ body: { + id: 'my-search-id', is_partial: false, is_running: false, took: 162, @@ -54,6 +55,7 @@ describe('EQL search strategy', () => { describe('search()', () => { let mockEqlSearch: jest.Mock; let mockEqlGet: jest.Mock; + let mockEqlDelete: jest.Mock; let mockDeps: SearchStrategyDependencies; let params: Required['params']; let options: Required['options']; @@ -61,6 +63,8 @@ describe('EQL search strategy', () => { beforeEach(() => { mockEqlSearch = jest.fn().mockResolvedValueOnce(getMockEqlResponse()); mockEqlGet = jest.fn().mockResolvedValueOnce(getMockEqlResponse()); + mockEqlDelete = jest.fn(); + mockDeps = { uiSettingsClient: { get: jest.fn(), @@ -70,6 +74,7 @@ describe('EQL search strategy', () => { eql: { get: mockEqlGet, search: mockEqlSearch, + delete: mockEqlDelete, }, }, }, @@ -124,6 +129,34 @@ describe('EQL search strategy', () => { }); }); + it('should delete when aborted', async () => { + const response = getMockEqlResponse(); + mockEqlSearch.mockReset().mockResolvedValueOnce({ + ...response, + body: { + ...response.body, + is_running: true, + }, + }); + const eqlSearch = await eqlSearchStrategyProvider(mockSearchConfig, mockLogger); + const abortController = new AbortController(); + const abortSignal = abortController.signal; + + // Abort after an incomplete first response is returned + setTimeout(() => abortController.abort(), 100); + + let err: any; + try { + await eqlSearch.search({ options, params }, { abortSignal }, mockDeps).toPromise(); + } catch (e) { + err = e; + } + + expect(mockEqlSearch).toBeCalled(); + expect(err).not.toBeUndefined(); + expect(mockEqlDelete).toBeCalled(); + }); + describe('arguments', () => { it('sends along async search options', async () => { const eqlSearch = await eqlSearchStrategyProvider(mockSearchConfig, mockLogger); diff --git a/src/plugins/data/server/search/strategies/eql_search/eql_search_strategy.ts b/src/plugins/data/server/search/strategies/eql_search/eql_search_strategy.ts index 9dd24e67917196..00b8cfdeb52e5c 100644 --- a/src/plugins/data/server/search/strategies/eql_search/eql_search_strategy.ts +++ b/src/plugins/data/server/search/strategies/eql_search/eql_search_strategy.ts @@ -9,6 +9,7 @@ import type { TransportResult } from '@elastic/elasticsearch'; import { tap } from 'rxjs/operators'; import type { IScopedClusterClient, Logger } from '@kbn/core/server'; +import { getKbnServerError } from '@kbn/kibana-utils-plugin/server'; import { SearchConfigSchema } from '../../../../config'; import { EqlSearchStrategyRequest, @@ -27,15 +28,19 @@ export const eqlSearchStrategyProvider = ( searchConfig: SearchConfigSchema, logger: Logger ): ISearchStrategy => { - async function cancelAsyncSearch(id: string, esClient: IScopedClusterClient) { + function cancelAsyncSearch(id: string, esClient: IScopedClusterClient) { const client = esClient.asCurrentUser.eql; - await client.delete({ id }); + return client.delete({ id }); } return { cancel: async (id, options, { esClient }) => { logger.debug(`_eql/delete ${id}`); - await cancelAsyncSearch(id, esClient); + try { + await cancelAsyncSearch(id, esClient); + } catch (e) { + throw getKbnServerError(e); + } }, search: ({ id, ...request }, options: IAsyncSearchOptions, { esClient, uiSettingsClient }) => { @@ -85,8 +90,15 @@ export const eqlSearchStrategyProvider = ( }; const cancel = async () => { - if (id) { + if (!id) return; + try { await cancelAsyncSearch(id, esClient); + } catch (e) { + // A 404 means either this search request does not exist, or that it is already cancelled + if (e.meta?.statusCode === 404) return; + + // Log all other (unexpected) error messages + logger.error(`cancelEqlSearch error: ${e.message}`); } }; diff --git a/src/plugins/data/server/search/strategies/ese_search/ese_search_strategy.test.ts b/src/plugins/data/server/search/strategies/ese_search/ese_search_strategy.test.ts index 3b2c5e8e0e5c8b..33987c09d88ddf 100644 --- a/src/plugins/data/server/search/strategies/ese_search/ese_search_strategy.test.ts +++ b/src/plugins/data/server/search/strategies/ese_search/ese_search_strategy.test.ts @@ -136,6 +136,30 @@ describe('ES search strategy', () => { expect(request).toHaveProperty('keep_alive', '60000ms'); }); + it('allows overriding keep_alive and wait_for_completion_timeout', async () => { + mockGetCaller.mockResolvedValueOnce(mockAsyncResponse); + + const params = { + index: 'logstash-*', + body: { query: {} }, + wait_for_completion_timeout: '10s', + keep_alive: '5m', + }; + const esSearch = await enhancedEsSearchStrategyProvider( + mockLegacyConfig$, + mockSearchConfig, + mockLogger + ); + + await esSearch.search({ id: 'foo', params }, {}, mockDeps).toPromise(); + + expect(mockGetCaller).toBeCalled(); + const request = mockGetCaller.mock.calls[0][0]; + expect(request.id).toEqual('foo'); + expect(request).toHaveProperty('wait_for_completion_timeout', '10s'); + expect(request).toHaveProperty('keep_alive', '5m'); + }); + it('sets transport options on POST requests', async () => { const transportOptions = { maxRetries: 1 }; mockSubmitCaller.mockResolvedValueOnce(mockAsyncResponse); @@ -260,6 +284,38 @@ describe('ES search strategy', () => { expect(mockApiCaller).toBeCalledTimes(0); }); + + it('should delete when aborted', async () => { + mockSubmitCaller.mockResolvedValueOnce({ + ...mockAsyncResponse, + body: { + ...mockAsyncResponse.body, + is_running: true, + }, + }); + + const params = { index: 'logstash-*', body: { query: {} } }; + const esSearch = await enhancedEsSearchStrategyProvider( + mockLegacyConfig$, + mockSearchConfig, + mockLogger + ); + const abortController = new AbortController(); + const abortSignal = abortController.signal; + + // Abort after an incomplete first response is returned + setTimeout(() => abortController.abort(), 100); + + let err: KbnServerError | undefined; + try { + await esSearch.search({ params }, { abortSignal }, mockDeps).toPromise(); + } catch (e) { + err = e; + } + expect(mockSubmitCaller).toBeCalled(); + expect(err).not.toBeUndefined(); + expect(mockDeleteCaller).toBeCalled(); + }); }); describe('with sessionId', () => { @@ -367,6 +423,44 @@ describe('ES search strategy', () => { expect(request).toHaveProperty('wait_for_completion_timeout'); expect(request).not.toHaveProperty('keep_alive'); }); + + it('should not delete a saved session when aborted', async () => { + mockSubmitCaller.mockResolvedValueOnce({ + ...mockAsyncResponse, + body: { + ...mockAsyncResponse.body, + is_running: true, + }, + }); + + const params = { index: 'logstash-*', body: { query: {} } }; + const esSearch = await enhancedEsSearchStrategyProvider( + mockLegacyConfig$, + mockSearchConfig, + mockLogger + ); + const abortController = new AbortController(); + const abortSignal = abortController.signal; + + // Abort after an incomplete first response is returned + setTimeout(() => abortController.abort(), 100); + + let err: KbnServerError | undefined; + try { + await esSearch + .search( + { params }, + { abortSignal, sessionId: '1', isSearchStored: true, isStored: true }, + mockDeps + ) + .toPromise(); + } catch (e) { + err = e; + } + expect(mockSubmitCaller).toBeCalled(); + expect(err).not.toBeUndefined(); + expect(mockDeleteCaller).not.toBeCalled(); + }); }); it('throws normalized error if ResponseError is thrown', async () => { 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 89699d7d58611f..174f9924f1cc7b 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 @@ -12,6 +12,7 @@ import { catchError, tap } from 'rxjs/operators'; 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 type { ISearchStrategy, SearchStrategyDependencies } from '../../types'; import type { @@ -43,18 +44,14 @@ export const enhancedEsSearchStrategyProvider = ( logger: Logger, usage?: SearchUsage, useInternalUser: boolean = false -): ISearchStrategy => { - async function cancelAsyncSearch(id: string, esClient: IScopedClusterClient) { - try { - const client = useInternalUser ? esClient.asInternalUser : esClient.asCurrentUser; - await client.asyncSearch.delete({ id }); - } catch (e) { - throw getKbnServerError(e); - } +): ISearchStrategy> => { + function cancelAsyncSearch(id: string, esClient: IScopedClusterClient) { + const client = useInternalUser ? esClient.asInternalUser : esClient.asCurrentUser; + return client.asyncSearch.delete({ id }); } function asyncSearch( - { id, ...request }: IEsSearchRequest, + { id, ...request }: IEsSearchRequest, options: IAsyncSearchOptions, { esClient, uiSettingsClient }: SearchStrategyDependencies ) { @@ -62,7 +59,13 @@ export const enhancedEsSearchStrategyProvider = ( const search = async () => { const params = id - ? getDefaultAsyncGetParams(searchConfig, options) + ? { + ...getDefaultAsyncGetParams(searchConfig, options), + ...(request.params?.keep_alive ? { keep_alive: request.params.keep_alive } : {}), + ...(request.params?.wait_for_completion_timeout + ? { wait_for_completion_timeout: request.params.wait_for_completion_timeout } + : {}), + } : { ...(await getDefaultAsyncSubmitParams(uiSettingsClient, searchConfig, options)), ...request.params, @@ -89,8 +92,15 @@ export const enhancedEsSearchStrategyProvider = ( }; const cancel = async () => { - if (id) { + if (!id || options.isStored) return; + try { await cancelAsyncSearch(id, esClient); + } catch (e) { + // A 404 means either this search request does not exist, or that it is already cancelled + if (e.meta?.statusCode === 404) return; + + // Log all other (unexpected) error messages + logger.error(`cancelAsyncSearch error: ${e.message}`); } }; @@ -179,7 +189,11 @@ export const enhancedEsSearchStrategyProvider = ( */ cancel: async (id, options, { esClient }) => { logger.debug(`cancel ${id}`); - await cancelAsyncSearch(id, esClient); + try { + await cancelAsyncSearch(id, esClient); + } catch (e) { + throw getKbnServerError(e); + } }, /** * diff --git a/src/plugins/data/server/search/strategies/ese_search/types.ts b/src/plugins/data/server/search/strategies/ese_search/types.ts index 4116aa43803397..5ff324e1c2e4f0 100644 --- a/src/plugins/data/server/search/strategies/ese_search/types.ts +++ b/src/plugins/data/server/search/strategies/ese_search/types.ts @@ -6,11 +6,21 @@ * Side Public License, v 1. */ -import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import type { + AsyncSearchGetRequest, + SearchResponse, + ShardStatistics, +} from '@elastic/elasticsearch/lib/api/types'; +import { ISearchRequestParams } from '../../../../common'; + +export interface IAsyncSearchRequestParams extends ISearchRequestParams { + keep_alive?: AsyncSearchGetRequest['keep_alive']; + wait_for_completion_timeout?: AsyncSearchGetRequest['wait_for_completion_timeout']; +} export interface AsyncSearchResponse { id?: string; - response: estypes.SearchResponse; + response: SearchResponse; start_time_in_millis: number; expiration_time_in_millis: number; is_partial: boolean; @@ -18,5 +28,5 @@ export interface AsyncSearchResponse { } export interface AsyncSearchStatusResponse extends Omit { completion_status: number; - _shards: estypes.ShardStatistics; + _shards: ShardStatistics; } diff --git a/src/plugins/data/server/search/strategies/sql_search/sql_search_strategy.test.ts b/src/plugins/data/server/search/strategies/sql_search/sql_search_strategy.test.ts index 700c658de10c04..36fb43a34894ff 100644 --- a/src/plugins/data/server/search/strategies/sql_search/sql_search_strategy.test.ts +++ b/src/plugins/data/server/search/strategies/sql_search/sql_search_strategy.test.ts @@ -124,6 +124,33 @@ describe('SQL search strategy', () => { signal: undefined, }); }); + + it('should delete when aborted', async () => { + mockSqlQuery.mockResolvedValueOnce({ + ...mockSqlResponse, + body: { + ...mockSqlResponse.body, + is_running: true, + }, + }); + const esSearch = await sqlSearchStrategyProvider(mockSearchConfig, mockLogger); + const abortController = new AbortController(); + const abortSignal = abortController.signal; + + // Abort after an incomplete first response is returned + setTimeout(() => abortController.abort(), 100); + + let err: any; + try { + await esSearch.search({ params: {} }, { abortSignal }, mockDeps).toPromise(); + } catch (e) { + err = e; + } + + expect(mockSqlQuery).toBeCalled(); + expect(err).not.toBeUndefined(); + expect(mockSqlDelete).toBeCalled(); + }); }); // skip until full search session support https://github.com/elastic/kibana/issues/127880 diff --git a/src/plugins/data/server/search/strategies/sql_search/sql_search_strategy.ts b/src/plugins/data/server/search/strategies/sql_search/sql_search_strategy.ts index 87b29f5438efb1..9e04675d12247f 100644 --- a/src/plugins/data/server/search/strategies/sql_search/sql_search_strategy.ts +++ b/src/plugins/data/server/search/strategies/sql_search/sql_search_strategy.ts @@ -29,13 +29,9 @@ export const sqlSearchStrategyProvider = ( logger: Logger, useInternalUser: boolean = false ): ISearchStrategy => { - async function cancelAsyncSearch(id: string, esClient: IScopedClusterClient) { - try { - const client = useInternalUser ? esClient.asInternalUser : esClient.asCurrentUser; - await client.sql.deleteAsync({ id }); - } catch (e) { - throw getKbnServerError(e); - } + function cancelAsyncSearch(id: string, esClient: IScopedClusterClient) { + const client = useInternalUser ? esClient.asInternalUser : esClient.asCurrentUser; + return client.sql.deleteAsync({ id }); } function asyncSearch( @@ -92,8 +88,15 @@ export const sqlSearchStrategyProvider = ( }; const cancel = async () => { - if (id) { + if (!id) return; + try { await cancelAsyncSearch(id, esClient); + } catch (e) { + // A 404 means either this search request does not exist, or that it is already cancelled + if (e.meta?.statusCode === 404) return; + + // Log all other (unexpected) error messages + logger.error(`cancelSqlSearch error: ${e.message}`); } }; @@ -130,7 +133,11 @@ export const sqlSearchStrategyProvider = ( */ cancel: async (id, options, { esClient }) => { logger.debug(`sql search: cancel async_search_id=${id}`); - await cancelAsyncSearch(id, esClient); + try { + await cancelAsyncSearch(id, esClient); + } catch (e) { + throw getKbnServerError(e); + } }, /** * diff --git a/src/plugins/data_views/server/data_views_service_factory.ts b/src/plugins/data_views/server/data_views_service_factory.ts index e5324b2eb02d27..430ebd36489b6d 100644 --- a/src/plugins/data_views/server/data_views_service_factory.ts +++ b/src/plugins/data_views/server/data_views_service_factory.ts @@ -63,13 +63,21 @@ export const dataViewsServiceFactory = (deps: DataViewsServiceFactoryDeps) => byPassCapabilities ? true : request - ? (await capabilities.resolveCapabilities(request)).indexPatterns.save === true + ? ( + await capabilities.resolveCapabilities(request, { + capabilityPath: 'indexPatterns.save', + }) + ).indexPatterns.save === true : false, getCanSaveAdvancedSettings: async () => byPassCapabilities ? true : request - ? (await capabilities.resolveCapabilities(request)).advancedSettings.save === true + ? ( + await capabilities.resolveCapabilities(request, { + capabilityPath: 'advancedSettings.save', + }) + ).advancedSettings.save === true : false, scriptedFieldsEnabled: deps.scriptedFieldsEnabled, }); diff --git a/src/plugins/discover/public/application/main/components/layout/discover_documents.tsx b/src/plugins/discover/public/application/main/components/layout/discover_documents.tsx index bf01e2a7b66697..f655af89c0c4cb 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_documents.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_documents.tsx @@ -37,7 +37,6 @@ import { SHOW_MULTIFIELDS, SORT_DEFAULT_ORDER_SETTING, } from '@kbn/discover-utils'; -import { i18n } from '@kbn/i18n'; import useObservable from 'react-use/lib/useObservable'; import type { DocViewFilterFn } from '@kbn/unified-doc-viewer/types'; import { DiscoverGrid } from '../../../../components/discover_grid'; @@ -65,7 +64,6 @@ import { DiscoverGridFlyout } from '../../../../components/discover_grid_flyout' import { getRenderCustomToolbarWithElements } from '../../../../components/discover_grid/render_custom_toolbar'; import { useSavedSearchInitial } from '../../services/discover_state_provider'; import { useFetchMoreRecords } from './use_fetch_more_records'; -import { ErrorCallout } from '../../../../components/common/error_callout'; import { SelectedVSAvailableCallout } from './selected_vs_available_callout'; const containerStyles = css` @@ -256,22 +254,11 @@ function DiscoverDocumentsComponent({ [dataView, onAddColumn, onAddFilter, onRemoveColumn, query, savedSearch.id, setExpandedDoc] ); - const dataState = useDataState(stateContainer.dataState.data$.main$); const documents = useObservable(stateContainer.dataState.data$.documents$); const callouts = useMemo( () => ( <> - {dataState.error && ( - - )} ), [ - dataState.error, isTextBasedQuery, currentColumns, documents?.textBasedQueryColumns, diff --git a/src/plugins/discover/public/application/main/components/layout/discover_layout.tsx b/src/plugins/discover/public/application/main/components/layout/discover_layout.tsx index 6461603609903e..b2d3a4b5058ee9 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_layout.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_layout.tsx @@ -114,8 +114,8 @@ export function DiscoverLayout({ stateContainer }: DiscoverLayoutProps) { const isPlainRecord = useMemo(() => getRawRecordType(query) === RecordRawType.PLAIN, [query]); const resultState = useMemo( - () => getResultState(dataState.fetchStatus, dataState.foundDocuments!, isPlainRecord), - [dataState.fetchStatus, dataState.foundDocuments, isPlainRecord] + () => getResultState(dataState.fetchStatus, dataState.foundDocuments ?? false), + [dataState.fetchStatus, dataState.foundDocuments] ); const onOpenInspector = useInspector({ @@ -338,7 +338,6 @@ export function DiscoverLayout({ stateContainer }: DiscoverLayoutProps) { } )} error={dataState.error} - data-test-subj="discoverNoResultsError" /> ) : ( { - test('fetching uninitialized', () => { + test(`should return 'uninitialized' when fetching uninitialized`, () => { const actual = getResultState(FetchStatus.UNINITIALIZED, false); expect(actual).toBe(resultStatuses.UNINITIALIZED); }); - test('fetching complete with no records', () => { + test(`should return 'loading' when fetching is loading`, () => { + const actual = getResultState(FetchStatus.LOADING, false); + expect(actual).toBe(resultStatuses.LOADING); + }); + + test(`should return 'none' when fetching is complete with no records`, () => { const actual = getResultState(FetchStatus.COMPLETE, false); expect(actual).toBe(resultStatuses.NO_RESULTS); }); - test('fetching ongoing aka loading', () => { - const actual = getResultState(FetchStatus.LOADING, false); - expect(actual).toBe(resultStatuses.LOADING); + test(`should return 'none' after a fetch error`, () => { + const actual = getResultState(FetchStatus.ERROR, false); + expect(actual).toBe(resultStatuses.NO_RESULTS); }); - test('fetching ready', () => { + test(`should return 'ready' when fetching completes with records`, () => { const actual = getResultState(FetchStatus.COMPLETE, true); expect(actual).toBe(resultStatuses.READY); }); - test('re-fetching after already data is available', () => { + test(`should reurn 'ready' when re-fetching after already data is available`, () => { const actual = getResultState(FetchStatus.LOADING, true); expect(actual).toBe(resultStatuses.READY); }); - test('after a fetch error when data was successfully fetched before ', () => { + test(`should return 'none' after a fetch error when data was successfully fetched before`, () => { const actual = getResultState(FetchStatus.ERROR, true); - expect(actual).toBe(resultStatuses.READY); + expect(actual).toBe(resultStatuses.NO_RESULTS); }); }); diff --git a/src/plugins/discover/public/application/main/utils/get_result_state.ts b/src/plugins/discover/public/application/main/utils/get_result_state.ts index ff31e114754ad4..8d39707c65f9cf 100644 --- a/src/plugins/discover/public/application/main/utils/get_result_state.ts +++ b/src/plugins/discover/public/application/main/utils/get_result_state.ts @@ -18,15 +18,11 @@ export const resultStatuses = { * Returns the current state of the result, depends on fetchStatus and the given fetched rows * Determines what is displayed in Discover main view (loading view, data view, empty data view, ...) */ -export function getResultState( - fetchStatus: FetchStatus, - foundDocuments: boolean = false, - isPlainRecord?: boolean -) { +export function getResultState(fetchStatus: FetchStatus, foundDocuments: boolean = false) { if (fetchStatus === FetchStatus.UNINITIALIZED) { return resultStatuses.UNINITIALIZED; } - if (isPlainRecord && fetchStatus === FetchStatus.ERROR) return resultStatuses.NO_RESULTS; + if (fetchStatus === FetchStatus.ERROR) return resultStatuses.NO_RESULTS; if (!foundDocuments && fetchStatus === FetchStatus.LOADING) return resultStatuses.LOADING; else if (foundDocuments) return resultStatuses.READY; diff --git a/src/plugins/discover/public/components/common/error_callout.test.tsx b/src/plugins/discover/public/components/common/error_callout.test.tsx index ec01af15cd7e2e..fd07af1bd548d9 100644 --- a/src/plugins/discover/public/components/common/error_callout.test.tsx +++ b/src/plugins/discover/public/components/common/error_callout.test.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { EuiButton, EuiCallOut, EuiEmptyPrompt, EuiLink, EuiModal } from '@elastic/eui'; +import { EuiButton, EuiEmptyPrompt } from '@elastic/eui'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { findTestSubject } from '@kbn/test-jest-helpers'; import { mount } from 'enzyme'; @@ -37,14 +37,11 @@ describe('ErrorCallout', () => { it('should render', () => { const title = 'Error title'; const error = new Error('My error'); - const wrapper = mountWithServices( - - ); + const wrapper = mountWithServices(); const prompt = wrapper.find(EuiEmptyPrompt); expect(prompt).toHaveLength(1); expect(prompt.prop('title')).toBeDefined(); expect(prompt.prop('title')).not.toBeInstanceOf(String); - expect(prompt.prop('data-test-subj')).toBe('errorCallout'); expect(prompt.prop('body')).toBeDefined(); expect(findTestSubject(prompt, 'discoverErrorCalloutTitle').contains(title)).toBe(true); expect(findTestSubject(prompt, 'discoverErrorCalloutMessage').contains(error.message)).toBe( @@ -53,97 +50,31 @@ describe('ErrorCallout', () => { expect(prompt.find(EuiButton)).toHaveLength(1); }); - it('should render inline', () => { - const title = 'Error title'; - const error = new Error('My error'); - const wrapper = mountWithServices( - - ); - const callout = wrapper.find(EuiCallOut); - expect(callout).toHaveLength(1); - expect(callout.prop('title')).toBeDefined(); - expect(callout.prop('title')).not.toBeInstanceOf(String); - expect(callout.prop('size')).toBe('s'); - expect(callout.prop('data-test-subj')).toBe('errorCallout'); - expect( - findTestSubject(callout, 'discoverErrorCalloutMessage').contains(`${title}: ${error.message}`) - ).toBe(true); - expect(callout.find(EuiLink)).toHaveLength(1); - }); - it('should render with override display', () => { const title = 'Override title'; const error = new Error('My error'); const overrideDisplay =
Override display
; mockGetSearchErrorOverrideDisplay.mockReturnValue({ title, body: overrideDisplay }); - const wrapper = mountWithServices( - - ); + const wrapper = mountWithServices(); const prompt = wrapper.find(EuiEmptyPrompt); expect(prompt).toHaveLength(1); expect(prompt.prop('title')).toBeDefined(); expect(prompt.prop('title')).not.toBeInstanceOf(String); - expect(prompt.prop('data-test-subj')).toBe('errorCallout'); expect(prompt.prop('body')).toBeDefined(); expect(findTestSubject(prompt, 'discoverErrorCalloutTitle').contains(title)).toBe(true); expect(prompt.contains(overrideDisplay)).toBe(true); expect(prompt.find(EuiButton)).toHaveLength(0); }); - it('should render with override display and inline', () => { - const title = 'Override title'; - const error = new Error('My error'); - const overrideDisplay =
Override display
; - mockGetSearchErrorOverrideDisplay.mockReturnValue({ title, body: overrideDisplay }); - const wrapper = mountWithServices( - - ); - const callout = wrapper.find(EuiCallOut); - expect(callout).toHaveLength(1); - expect(callout.prop('title')).toBeDefined(); - expect(callout.prop('title')).not.toBeInstanceOf(String); - expect(callout.prop('size')).toBe('s'); - expect(callout.prop('data-test-subj')).toBe('errorCallout'); - expect(callout.find(EuiLink)).toHaveLength(1); - expect(wrapper.find(EuiModal)).toHaveLength(0); - expect(wrapper.contains(title)).toBe(true); - expect(wrapper.contains(overrideDisplay)).toBe(false); - callout.find(EuiLink).simulate('click'); - expect(wrapper.find(EuiModal)).toHaveLength(1); - expect(findTestSubject(wrapper, 'discoverErrorCalloutOverrideModalTitle').contains(title)).toBe( - true - ); - expect( - findTestSubject(wrapper, 'discoverErrorCalloutOverrideModalBody').contains(overrideDisplay) - ).toBe(true); - expect(wrapper.contains(overrideDisplay)).toBe(true); - }); - it('should call showErrorDialog when the button is clicked', () => { (discoverServiceMock.core.notifications.showErrorDialog as jest.Mock).mockClear(); const title = 'Error title'; const error = new Error('My error'); - const wrapper = mountWithServices( - - ); + const wrapper = mountWithServices(); wrapper.find(EuiButton).find('button').simulate('click'); expect(discoverServiceMock.core.notifications.showErrorDialog).toHaveBeenCalledWith({ title, error, }); }); - - it('should call showErrorDialog when the button is clicked inline', () => { - (discoverServiceMock.core.notifications.showErrorDialog as jest.Mock).mockClear(); - const title = 'Error title'; - const error = new Error('My error'); - const wrapper = mountWithServices( - - ); - wrapper.find(EuiLink).find('button').simulate('click'); - expect(discoverServiceMock.core.notifications.showErrorDialog).toHaveBeenCalledWith({ - title, - error, - }); - }); }); diff --git a/src/plugins/discover/public/components/common/error_callout.tsx b/src/plugins/discover/public/components/common/error_callout.tsx index 84b5b979c0249b..1b3e8f02f39a31 100644 --- a/src/plugins/discover/public/components/common/error_callout.tsx +++ b/src/plugins/discover/public/components/common/error_callout.tsx @@ -6,143 +6,58 @@ * Side Public License, v 1. */ -import { - EuiButton, - EuiCallOut, - EuiEmptyPrompt, - EuiLink, - EuiModal, - EuiModalBody, - EuiModalHeader, - EuiModalHeaderTitle, - EuiText, - useEuiTheme, -} from '@elastic/eui'; +import { EuiButton, EuiEmptyPrompt, useEuiTheme } from '@elastic/eui'; import { css } from '@emotion/react'; import { getSearchErrorOverrideDisplay } from '@kbn/data-plugin/public'; import { i18n } from '@kbn/i18n'; -import React, { ReactNode, useState } from 'react'; +import React from 'react'; import { useDiscoverServices } from '../../hooks/use_discover_services'; -export interface ErrorCalloutProps { +interface Props { title: string; error: Error; - inline?: boolean; - 'data-test-subj'?: string; } -export const ErrorCallout = ({ - title, - error, - inline, - 'data-test-subj': dataTestSubj, -}: ErrorCalloutProps) => { +export const ErrorCallout = ({ title, error }: Props) => { const { core } = useDiscoverServices(); const { euiTheme } = useEuiTheme(); - const showErrorMessage = i18n.translate('discover.errorCalloutShowErrorMessage', { - defaultMessage: 'View details', - }); - const overrideDisplay = getSearchErrorOverrideDisplay({ error, application: core.application, }); - const [overrideModalOpen, setOverrideModalOpen] = useState(false); - - const showError = overrideDisplay?.body - ? () => setOverrideModalOpen(true) - : () => core.notifications.showErrorDialog({ title, error }); - - let formattedTitle: ReactNode = overrideDisplay?.title || title; - - if (inline) { - const formattedTitleMessage = overrideDisplay - ? formattedTitle - : i18n.translate('discover.errorCalloutFormattedTitle', { - defaultMessage: '{title}: {errorMessage}', - values: { title, errorMessage: error.message }, - }); - - formattedTitle = ( - <> - - {formattedTitleMessage} - - - {showErrorMessage} - - - ); - } - return ( - <> - {inline ? ( - {overrideDisplay?.title ?? title}} + body={ +
- ) : ( - {formattedTitle}} - body={ -
- {overrideDisplay?.body ?? ( - <> -

- {error.message} -

- {showErrorMessage} - - )} -
- } - data-test-subj={dataTestSubj} - /> - )} - {overrideDisplay && overrideModalOpen && ( - setOverrideModalOpen(false)}> - - - {overrideDisplay.title} - - - - - {overrideDisplay.body} - - - - )} - + > + {overrideDisplay?.body ?? ( + <> +

+ {error.message} +

+ core.notifications.showErrorDialog({ title, error })}> + {i18n.translate('discover.errorCalloutShowErrorMessage', { + defaultMessage: 'View details', + })} + + + )} +
+ } + /> ); }; diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable_renderer.test.tsx b/src/plugins/embeddable/public/lib/embeddables/embeddable_renderer.test.tsx index b919672ad01e3c..a1be9c560bf3c4 100644 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable_renderer.test.tsx +++ b/src/plugins/embeddable/public/lib/embeddables/embeddable_renderer.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { waitFor } from '@testing-library/dom'; +import { waitFor } from '@testing-library/react'; import { render } from '@testing-library/react'; import { renderHook } from '@testing-library/react-hooks'; import { diff --git a/src/plugins/files/common/types.ts b/src/plugins/files/common/types.ts index 8b6059bdb5637a..e2efdb3bdea336 100644 --- a/src/plugins/files/common/types.ts +++ b/src/plugins/files/common/types.ts @@ -20,6 +20,7 @@ import type { } from '@kbn/shared-ux-file-types'; import type { UploadOptions } from '../server/blob_storage_service'; import type { ES_FIXED_SIZE_INDEX_BLOB_STORE } from './constants'; +import type { SupportedFileHashAlgorithm } from '../server/saved_objects/file'; export type { FileKindBase, @@ -94,6 +95,12 @@ export interface FileKind extends FileKindBase { */ share?: HttpEndpointDefinition; }; + + /** + * A list of hashes to compute for this file kind. The hashes will be computed + * during the file upload process and stored in the file metadata. + */ + hashes?: SupportedFileHashAlgorithm[]; } /** Definition for an endpoint that the File's service will generate */ diff --git a/src/plugins/files/server/file/file.ts b/src/plugins/files/server/file/file.ts index eeec150cfc78c6..495d233fd421ba 100644 --- a/src/plugins/files/server/file/file.ts +++ b/src/plugins/files/server/file/file.ts @@ -19,7 +19,6 @@ import { Observable, lastValueFrom, } from 'rxjs'; -import { isFileHashTransform } from '../file_client/stream_transforms/file_hash_transform/file_hash_transform'; import { UploadOptions } from '../blob_storage_service'; import type { FileShareJSON, FileShareJSONWithToken } from '../../common/types'; import type { File as IFile, UpdatableFileMetadata, FileJSON } from '../../common'; @@ -72,10 +71,7 @@ export class File implements IFile { return this; } - private upload( - content: Readable, - options?: Partial> - ): Observable<{ size: number }> { + private upload(content: Readable, options?: Partial>) { return defer(() => this.fileClient.upload(this.metadata, content, options)); } @@ -104,26 +100,17 @@ export class File implements IFile { ) ) ), - mergeMap(({ size }) => { + mergeMap(({ size, hashes }) => { const updatedStateAction: Action & { action: 'uploaded' } = { action: 'uploaded', payload: { size }, }; - if (options && options.transforms) { - options.transforms.some((transform) => { - if (isFileHashTransform(transform)) { - const fileHash = transform.getFileHash(); - - updatedStateAction.payload.hash = { - [fileHash.algorithm]: fileHash.value, - }; - - return true; - } - - return false; - }); + if (hashes && hashes.length) { + updatedStateAction.payload.hash = {}; + for (const { algorithm, value } of hashes) { + updatedStateAction.payload.hash[algorithm] = value; + } } return this.updateFileState(updatedStateAction); diff --git a/src/plugins/files/server/file_client/create_es_file_client.ts b/src/plugins/files/server/file_client/create_es_file_client.ts index 755071d66328c7..f9ada86768c2fc 100644 --- a/src/plugins/files/server/file_client/create_es_file_client.ts +++ b/src/plugins/files/server/file_client/create_es_file_client.ts @@ -70,6 +70,7 @@ export function createEsFileClient(arg: CreateEsFileClientArgs): FileClient { id: NO_FILE_KIND, http: {}, maxSizeBytes, + hashes: ['md5', 'sha1', 'sha256', 'sha512'], }, new EsIndexFilesMetadataClient(metadataIndex, elasticsearchClient, logger, indexIsAlias), new ElasticsearchBlobStorageClient( diff --git a/src/plugins/files/server/file_client/file_client.ts b/src/plugins/files/server/file_client/file_client.ts index 3bce97f6bcade5..26dbc90a44b90f 100644 --- a/src/plugins/files/server/file_client/file_client.ts +++ b/src/plugins/files/server/file_client/file_client.ts @@ -40,6 +40,9 @@ import { withReportPerformanceMetric, FILE_DOWNLOAD_PERFORMANCE_EVENT_NAME, } from '../performance'; +import { createFileHashTransform } from './stream_transforms/file_hash_transform'; +import { isFileHashTransform } from './stream_transforms/file_hash_transform/file_hash_transform'; +import { SupportedFileHashAlgorithm } from '../saved_objects/file'; export type UploadOptions = Omit; @@ -216,8 +219,8 @@ export class FileClientImpl implements FileClient { file: FileJSON, rs: Readable, options?: UploadOptions - ): ReturnType => { - const { maxSizeBytes } = this.fileKindDescriptor; + ): Promise => { + const { maxSizeBytes, hashes } = this.fileKindDescriptor; const { transforms = [], ...blobOptions } = options || {}; let maxFileSize: number = typeof maxSizeBytes === 'number' ? maxSizeBytes : fourMiB; @@ -231,11 +234,30 @@ export class FileClientImpl implements FileClient { transforms.push(enforceMaxByteSizeTransform(maxFileSize)); - return this.blobStorageClient.upload(rs, { + if (hashes && hashes.length) { + for (const hash of hashes) { + transforms.push(createFileHashTransform(hash)); + } + } + + const uploadResult = await this.blobStorageClient.upload(rs, { ...blobOptions, transforms, id: file.id, }); + + const result: UploadResult = { ...uploadResult, hashes: [] }; + + if (transforms && transforms.length) { + for (const transform of transforms) { + if (isFileHashTransform(transform)) { + const fileHash = transform.getFileHash(); + result.hashes.push(fileHash); + } + } + } + + return result; }; public download: BlobStorageClient['download'] = async (args) => { @@ -300,3 +322,12 @@ export class FileClientImpl implements FileClient { return this.internalFileShareService.list(args); }; } + +export interface UploadResult { + id: string; + size: number; + hashes: Array<{ + algorithm: SupportedFileHashAlgorithm; + value: string; + }>; +} diff --git a/src/plugins/files/server/file_client/integration_tests/es_file_client.test.ts b/src/plugins/files/server/file_client/integration_tests/es_file_client.test.ts index cbe80422e388bf..d4c6d268fa7dc5 100644 --- a/src/plugins/files/server/file_client/integration_tests/es_file_client.test.ts +++ b/src/plugins/files/server/file_client/integration_tests/es_file_client.test.ts @@ -97,6 +97,26 @@ describe('ES-index-backed file client', () => { await deleteFile({ id: file.id, hasContent: true }); }); + test('computes file hashes', async () => { + const file = await fileClient.create({ + id: '123', + metadata: { + name: 'cool name', + }, + }); + await file.uploadContent(Readable.from([Buffer.from('test')])); + + expect(file.toJSON().hash).toStrictEqual({ + md5: '098f6bcd4621d373cade4e832627b4f6', + sha1: 'a94a8fe5ccb19ba61c4c0873d391e987982fbbd3', + sha256: '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08', + sha512: + 'ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff', + }); + + await deleteFile({ id: file.id, hasContent: true }); + }); + test('searches across files', async () => { const { id: id1 } = await fileClient.create({ id: '123', diff --git a/src/plugins/files/server/plugin.ts b/src/plugins/files/server/plugin.ts index ba63e08b2ed211..9e608d8f38a596 100755 --- a/src/plugins/files/server/plugin.ts +++ b/src/plugins/files/server/plugin.ts @@ -139,6 +139,7 @@ export class FilesPlugin implements Plugin { + before(async () => { + await PageObjects.common.navigateToApp('errorBoundaryExample'); + await testSubjects.existOrFail('errorBoundaryExampleHeader'); + }); + + it('fatal error', async () => { + log.debug('clicking button for fatal error'); + await testSubjects.click('fatalErrorBtn'); + const errorHeader = await testSubjects.getVisibleText('errorBoundaryFatalHeader'); + expect(errorHeader).to.not.be(undefined); + + log.debug('checking that the error has taken over the page'); + await testSubjects.missingOrFail('errorBoundaryExampleHeader'); + + await testSubjects.click('errorBoundaryFatalShowDetailsBtn'); + const errorString = await testSubjects.getVisibleText('errorBoundaryFatalDetailsErrorString'); + expect(errorString).to.match(/Error: Example of unknown error type/); + + log.debug('closing error flyout'); + await testSubjects.click('euiFlyoutCloseButton'); + + log.debug('clicking page refresh'); + await testSubjects.click('errorBoundaryFatalPromptReloadBtn'); + + await retry.try(async () => { + log.debug('checking for page refresh'); + await testSubjects.existOrFail('errorBoundaryExampleHeader'); + }); + }); + + it('recoverable error', async () => { + log.debug('clicking button for recoverable error'); + await testSubjects.click('recoverableErrorBtn'); + const errorHeader = await testSubjects.getVisibleText('errorBoundaryRecoverableHeader'); + expect(errorHeader).to.not.be(undefined); + + log.debug('checking that the error has taken over the page'); + await testSubjects.missingOrFail('errorBoundaryExampleHeader'); + + log.debug('clicking page refresh'); + await testSubjects.click('errorBoundaryRecoverablePromptReloadBtn'); + + await retry.try(async () => { + log.debug('checking for page refresh'); + await testSubjects.existOrFail('errorBoundaryExampleHeader'); + }); + }); + }); +} diff --git a/test/functional/apps/discover/group1/_discover_histogram.ts b/test/functional/apps/discover/group1/_discover_histogram.ts index 6e30f75f90a103..d69bd153b3d564 100644 --- a/test/functional/apps/discover/group1/_discover_histogram.ts +++ b/test/functional/apps/discover/group1/_discover_histogram.ts @@ -289,8 +289,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // type an invalid search query, hit refresh await queryBar.setQuery('this is > not valid'); await queryBar.submitQuery(); - // check the error state - expect(await testSubjects.exists('embeddable-lens-failure')).to.be(true); + + await PageObjects.discover.showsErrorCallout(); // now remove the query await queryBar.clearQuery(); diff --git a/test/functional/apps/discover/group1/_errors.ts b/test/functional/apps/discover/group1/_errors.ts index ef68df5b472049..6f0a7ba96b7adf 100644 --- a/test/functional/apps/discover/group1/_errors.ts +++ b/test/functional/apps/discover/group1/_errors.ts @@ -32,7 +32,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('invalid scripted field error', () => { it('is rendered', async () => { - expect(await PageObjects.discover.noResultsErrorVisible()).to.be(true); + await PageObjects.discover.showsErrorCallout(); const painlessStackTrace = await testSubjects.find('painlessStackTrace'); expect(painlessStackTrace).not.to.be(undefined); }); diff --git a/test/functional/apps/discover/group1/_field_data.ts b/test/functional/apps/discover/group1/_field_data.ts index 0125549ead92fe..4047cfcdfc45d6 100644 --- a/test/functional/apps/discover/group1/_field_data.ts +++ b/test/functional/apps/discover/group1/_field_data.ts @@ -76,7 +76,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 'whitespace but "(" found.'; await queryBar.setQuery('xxx(yyy))'); await queryBar.submitQuery(); - expect(await PageObjects.discover.mainErrorVisible()).to.be(true); + await PageObjects.discover.showsErrorCallout(); const message = await PageObjects.discover.getDiscoverErrorMessage(); expect(message).to.contain(expectedError); }); diff --git a/test/functional/apps/discover/group1/_field_data_with_fields_api.ts b/test/functional/apps/discover/group1/_field_data_with_fields_api.ts index e262f3ce30c49b..7a21210a78e0c5 100644 --- a/test/functional/apps/discover/group1/_field_data_with_fields_api.ts +++ b/test/functional/apps/discover/group1/_field_data_with_fields_api.ts @@ -80,7 +80,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 'whitespace but "(" found.'; await queryBar.setQuery('xxx(yyy))'); await queryBar.submitQuery(); - expect(await PageObjects.discover.mainErrorVisible()).to.be(true); + await PageObjects.discover.showsErrorCallout(); const message = await PageObjects.discover.getDiscoverErrorMessage(); expect(message).to.contain(expectedError); }); diff --git a/test/functional/apps/discover/group2/_data_grid_copy_to_clipboard.ts b/test/functional/apps/discover/group2/_data_grid_copy_to_clipboard.ts index 06fe279dbd534e..27d847f9c49ce8 100644 --- a/test/functional/apps/discover/group2/_data_grid_copy_to_clipboard.ts +++ b/test/functional/apps/discover/group2/_data_grid_copy_to_clipboard.ts @@ -79,7 +79,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await dataGrid.clickCopyColumnName('@timestamp'); if (canReadClipboard) { const copiedTimestampName = await browser.getClipboardValue(); - expect(copiedTimestampName).to.be('"\'@timestamp"'); + expect(copiedTimestampName).to.be('@timestamp'); } expect(await toasts.getToastCount()).to.be(1); diff --git a/test/functional/apps/discover/group2/_data_grid_field_data.ts b/test/functional/apps/discover/group2/_data_grid_field_data.ts index 3347f398288a47..f12f378bbf94b0 100644 --- a/test/functional/apps/discover/group2/_data_grid_field_data.ts +++ b/test/functional/apps/discover/group2/_data_grid_field_data.ts @@ -91,7 +91,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 'whitespace but "(" found.'; await queryBar.setQuery('xxx(yyy))'); await queryBar.submitQuery(); - expect(await PageObjects.discover.mainErrorVisible()).to.be(true); + await PageObjects.discover.showsErrorCallout(); const message = await PageObjects.discover.getDiscoverErrorMessage(); expect(message).to.contain(expectedError); }); diff --git a/test/functional/apps/discover/group3/_sidebar.ts b/test/functional/apps/discover/group3/_sidebar.ts index b47e71f4528182..01e357b7f01e63 100644 --- a/test/functional/apps/discover/group3/_sidebar.ts +++ b/test/functional/apps/discover/group3/_sidebar.ts @@ -697,8 +697,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render even when retrieving documents failed with an error', async () => { await PageObjects.header.waitUntilLoadingHasFinished(); - await testSubjects.missingOrFail('discoverNoResultsError'); - expect(await PageObjects.unifiedFieldList.getSidebarAriaDescription()).to.be( INITIAL_FIELD_LIST_SUMMARY ); @@ -708,7 +706,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.header.waitUntilLoadingHasFinished(); // error in fetching documents because of the invalid runtime field - await testSubjects.existOrFail('discoverNoResultsError'); + await PageObjects.discover.showsErrorCallout(); await PageObjects.unifiedFieldList.waitUntilSidebarHasLoaded(); @@ -721,7 +719,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await browser.refresh(); await PageObjects.header.waitUntilLoadingHasFinished(); - await testSubjects.existOrFail('discoverNoResultsError'); // still has error + await PageObjects.discover.showsErrorCallout(); // still has error // check that the sidebar is rendered event after a refresh await PageObjects.unifiedFieldList.waitUntilSidebarHasLoaded(); @@ -731,8 +729,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.removeField('_invalid-runtimefield'); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.unifiedFieldList.waitUntilSidebarHasLoaded(); - - await testSubjects.missingOrFail('discoverNoResultsError'); }); it('should work correctly when time range is updated', async function () { diff --git a/test/functional/apps/discover/group3/_view_mode_toggle.ts b/test/functional/apps/discover/group3/_view_mode_toggle.ts index c47aad66c9a013..62c78efbc24320 100644 --- a/test/functional/apps/discover/group3/_view_mode_toggle.ts +++ b/test/functional/apps/discover/group3/_view_mode_toggle.ts @@ -79,13 +79,13 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await queryBar.submitQuery(); await PageObjects.header.waitUntilLoadingHasFinished(); - await testSubjects.existOrFail('discoverMainError'); + await PageObjects.discover.showsErrorCallout(); await queryBar.clearQuery(); await queryBar.submitQuery(); await PageObjects.header.waitUntilLoadingHasFinished(); - await testSubjects.missingOrFail('discoverMainError'); + await testSubjects.missingOrFail('discoverErrorCalloutTitle'); }); it('should show Field Statistics tab', async () => { diff --git a/test/functional/apps/discover/group4/_date_nested.ts b/test/functional/apps/discover/group4/_date_nested.ts index 9760645fe11c78..f761cdacc7fe95 100644 --- a/test/functional/apps/discover/group4/_date_nested.ts +++ b/test/functional/apps/discover/group4/_date_nested.ts @@ -10,7 +10,6 @@ import { FtrProviderContext } from '../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const esArchiver = getService('esArchiver'); - const testSubjects = getService('testSubjects'); const PageObjects = getPageObjects(['common', 'timePicker', 'discover']); const security = getService('security'); const kibanaServer = getService('kibanaServer'); @@ -34,7 +33,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should show an error message', async function () { await PageObjects.discover.selectIndexPattern('date-nested'); await PageObjects.discover.waitUntilSearchingHasFinished(); - await testSubjects.existOrFail('discoverNoResultsError'); + await PageObjects.discover.showsErrorCallout(); }); }); } diff --git a/test/functional/apps/discover/group4/_esql_view.ts b/test/functional/apps/discover/group4/_esql_view.ts index 773af0d512309b..2b6547152970d0 100644 --- a/test/functional/apps/discover/group4/_esql_view.ts +++ b/test/functional/apps/discover/group4/_esql_view.ts @@ -135,7 +135,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); }); describe('errors', () => { - it('should error messages for syntax errors in query', async function () { + it('should show error messages for syntax errors in query', async function () { await PageObjects.discover.selectTextBaseLang(); const brokenQueries = [ 'from logstash-* | limit 10*', @@ -149,7 +149,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.discover.waitUntilSearchingHasFinished(); // error in fetching documents because of the invalid query - await testSubjects.existOrFail('discoverNoResultsError'); + await PageObjects.discover.showsErrorCallout(); const message = await testSubjects.getVisibleText('discoverErrorCalloutMessage'); expect(message).to.contain( "[esql] > Couldn't parse Elasticsearch ES|QL query. Check your query and try again." diff --git a/test/functional/apps/visualize/group3/_annotation_listing.ts b/test/functional/apps/visualize/group3/_annotation_listing.ts index 71489388b54b57..53ae9d48e2b5c5 100644 --- a/test/functional/apps/visualize/group3/_annotation_listing.ts +++ b/test/functional/apps/visualize/group3/_annotation_listing.ts @@ -18,8 +18,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const retry = getService('retry'); const log = getService('log'); - // Failing: See https://github.com/elastic/kibana/issues/170578 - describe.skip('annotation listing page', function () { + describe('annotation listing page', function () { before(async function () { await kibanaServer.importExport.load( 'test/functional/fixtures/kbn_archiver/annotation_listing_page_search' @@ -122,8 +121,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await listingTable.expectItemsCount('eventAnnotation', 1); }); - // FLAKY: https://github.com/elastic/kibana/issues/170568 - describe.skip('individual annotations', () => { + describe('individual annotations', () => { it('edits an existing annotation', async function () { await listingTable.clickItemLink('eventAnnotation', 'edited title'); expect(await PageObjects.annotationEditor.getAnnotationCount()).to.be(1); @@ -156,11 +154,14 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); await PageObjects.annotationEditor.saveGroup(); - await listingTable.clearSearchFilter(); }); }); describe('data view switching', () => { + before(async () => { + await listingTable.clearSearchFilter(); + }); + it('recovers from missing data view', async () => { await listingTable.clickItemLink('eventAnnotation', 'missing data view'); diff --git a/test/functional/page_objects/annotation_library_editor_page.ts b/test/functional/page_objects/annotation_library_editor_page.ts index f5c66b9f6c1e9c..2a031d98429b32 100644 --- a/test/functional/page_objects/annotation_library_editor_page.ts +++ b/test/functional/page_objects/annotation_library_editor_page.ts @@ -10,6 +10,7 @@ import { FtrService } from '../ftr_provider_context'; export class AnnotationEditorPageObject extends FtrService { private readonly testSubjects = this.ctx.getService('testSubjects'); + private readonly find = this.ctx.getService('find'); private readonly retry = this.ctx.getService('retry'); /** @@ -56,12 +57,20 @@ export class AnnotationEditorPageObject extends FtrService { const queryInput = await this.testSubjects.find('annotation-query-based-query-input'); await queryInput.type(config.query); - await this.testSubjects.setValue('lnsXYThickness', '' + config.lineThickness); + const titles = await this.find.allByCssSelector( + '.euiFlyout h3.lnsDimensionEditorSection__heading' + ); + const lastTitle = titles[titles.length - 1]; + await lastTitle.click(); // close query input pop-up + await lastTitle.focus(); // scroll down to the bottom of the section await this.testSubjects.setValue( 'euiColorPickerAnchor indexPattern-dimension-colorPicker', config.color ); + await lastTitle.click(); // close color picker pop-up + + await this.testSubjects.setValue('lnsXYThickness', '' + config.lineThickness); await this.retry.waitFor('annotation editor UI to close', async () => { await this.testSubjects.click('backToGroupSettings'); diff --git a/test/functional/page_objects/common_page.ts b/test/functional/page_objects/common_page.ts index 4c71693dd41255..2c5b8bff30d479 100644 --- a/test/functional/page_objects/common_page.ts +++ b/test/functional/page_objects/common_page.ts @@ -417,18 +417,9 @@ export class CommonPageObject extends FtrService { * Clicks cancel button on modal * @param overlayWillStay pass in true if your test will show multiple modals in succession */ - async clickCancelOnModal(overlayWillStay = true, ignorePageLeaveWarning = false) { + async clickCancelOnModal(overlayWillStay = true) { this.log.debug('Clicking modal cancel'); - await this.testSubjects.exists('confirmModalTitleText'); - - await this.retry.try(async () => { - const warning = await this.testSubjects.exists('confirmModalTitleText'); - if (warning) { - await this.testSubjects.click( - ignorePageLeaveWarning ? 'confirmModalConfirmButton' : 'confirmModalCancelButton' - ); - } - }); + await this.testSubjects.click('confirmModalCancelButton'); if (!overlayWillStay) { await this.ensureModalOverlayHidden(); } diff --git a/test/functional/page_objects/discover_page.ts b/test/functional/page_objects/discover_page.ts index b20e055ea0d6a7..47a79132212cca 100644 --- a/test/functional/page_objects/discover_page.ts +++ b/test/functional/page_objects/discover_page.ts @@ -453,12 +453,8 @@ export class DiscoverPageObject extends FtrService { return await this.testSubjects.exists('discoverNoResultsTimefilter'); } - public noResultsErrorVisible() { - return this.testSubjects.exists('discoverNoResultsError'); - } - - public mainErrorVisible() { - return this.testSubjects.exists('discoverMainError'); + public showsErrorCallout() { + return this.testSubjects.existOrFail('discoverErrorCalloutTitle'); } public getDiscoverErrorMessage() { diff --git a/test/functional/services/common/browser.ts b/test/functional/services/common/browser.ts index 0a1798442f3608..4172e7087ea36f 100644 --- a/test/functional/services/common/browser.ts +++ b/test/functional/services/common/browser.ts @@ -6,18 +6,23 @@ * Side Public License, v 1. */ +import Url from 'url'; import { setTimeout as setTimeoutAsync } from 'timers/promises'; import { cloneDeepWith, isString } from 'lodash'; -import { Key, Origin, WebDriver } from 'selenium-webdriver'; +import { Key, Origin, type WebDriver } from 'selenium-webdriver'; import { Driver as ChromiumWebDriver } from 'selenium-webdriver/chrome'; import { modifyUrl } from '@kbn/std'; import sharp from 'sharp'; import { NoSuchSessionError } from 'selenium-webdriver/lib/error'; import { WebElementWrapper } from '../lib/web_element_wrapper'; -import { FtrProviderContext, FtrService } from '../../ftr_provider_context'; +import { type FtrProviderContext, FtrService } from '../../ftr_provider_context'; import { Browsers } from '../remote/browsers'; -import { NetworkOptions, NetworkProfile, NETWORK_PROFILES } from '../remote/network_profiles'; +import { + type NetworkOptions, + type NetworkProfile, + NETWORK_PROFILES, +} from '../remote/network_profiles'; export type Browser = BrowserService; @@ -164,17 +169,53 @@ class BrowserService extends FtrService { /** * Gets the URL that is loaded in the focused window/frame. * https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/webdriver_exports_WebDriver.html#getCurrentUrl - * + * @param relativeUrl (optional) set to true to return the relative URL (without the hostname and protocol) * @return {Promise} */ - public async getCurrentUrl() { + public async getCurrentUrl(relativeUrl: boolean = false): Promise { // strip _t=Date query param when url is read const current = await this.driver.getCurrentUrl(); const currentWithoutTime = modifyUrl(current, (parsed) => { delete (parsed.query as any)._t; return void 0; }); - return currentWithoutTime; + + if (relativeUrl) { + const { path } = Url.parse(currentWithoutTime); + return path!; // this property includes query params and anchors + } else { + return currentWithoutTime; + } + } + + /** + * Uses the 'retry' service and waits for the current browser URL to match the provided path. + * NB the provided path can contain query params as well as hash anchors. + * Using retry logic makes URL assertions less flaky + * @param expectedPath The relative path that we are expecting the browser to be on + * @returns a Promise that will reject if the browser URL does not match the expected one + */ + public async waitForUrlToBe(expectedPath: string) { + const retry = await this.ctx.getService('retry'); + const log = this.ctx.getService('log'); + + await retry.waitForWithTimeout(`URL to be ${expectedPath}`, 5000, async () => { + const currentPath = await this.getCurrentUrl(true); + + if (currentPath !== expectedPath) { + log.debug(`Expected URL to be ${expectedPath}, got ${currentPath}`); + } + return currentPath === expectedPath; + }); + + // wait some time before checking the URL again + await new Promise((resolve) => setTimeout(resolve, 1000)); + + // ensure the URL stays the same and we did not go through any redirects + const currentPath = await this.getCurrentUrl(true); + if (currentPath !== expectedPath) { + throw new Error(`Expected URL to continue to be ${expectedPath}, got ${currentPath}`); + } } /** diff --git a/test/plugin_functional/test_suites/core_plugins/application_deep_links.ts b/test/plugin_functional/test_suites/core_plugins/application_deep_links.ts index e4973b05bd9554..1d326bcdfef824 100644 --- a/test/plugin_functional/test_suites/core_plugins/application_deep_links.ts +++ b/test/plugin_functional/test_suites/core_plugins/application_deep_links.ts @@ -6,16 +6,14 @@ * Side Public License, v 1. */ -import url from 'url'; import expect from '@kbn/expect'; import type { PluginFunctionalProviderContext } from '../../services'; -export default function ({ getService, getPageObjects }: PluginFunctionalProviderContext) { - const PageObjects = getPageObjects(['common']); +export default function ({ getService, getPageObject }: PluginFunctionalProviderContext) { + const common = getPageObject('common'); const browser = getService('browser'); const appsMenu = getService('appsMenu'); const testSubjects = getService('testSubjects'); - const retry = getService('retry'); const esArchiver = getService('esArchiver'); const log = getService('log'); @@ -27,25 +25,6 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide await testSubjects.click(appLink); }; - const getKibanaUrl = (pathname?: string, search?: string) => - url.format({ - protocol: 'http:', - hostname: process.env.TEST_KIBANA_HOST || 'localhost', - port: process.env.TEST_KIBANA_PORT || '5620', - pathname, - search, - }); - - /** Use retry logic to make URL assertions less flaky */ - const waitForUrlToBe = (pathname?: string, search?: string) => { - const expectedUrl = getKibanaUrl(pathname, search); - return retry.waitFor(`Url to be ${expectedUrl}`, async () => { - const currentUrl = await browser.getCurrentUrl(); - log?.debug(`waiting for currentUrl ${currentUrl} to be expectedUrl ${expectedUrl}`); - return currentUrl === expectedUrl; - }); - }; - const loadingScreenNotShown = async () => expect(await testSubjects.exists('kbnLoadingMessage')).to.be(false); @@ -57,7 +36,7 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide describe('application deep links navigation', function describeDeepLinksTests() { before(async () => { await esArchiver.emptyKibanaIndex(); - await PageObjects.common.navigateToApp('dl'); + await common.navigateToApp('dl'); }); it('should start on home page', async () => { @@ -66,42 +45,42 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide it('should navigate to page A when navlink is clicked', async () => { await clickAppLink('PageA'); - await waitForUrlToBe('/app/dl/page-a'); + await browser.waitForUrlToBe('/app/dl/page-a'); await loadingScreenNotShown(); await checkAppVisible('PageA'); }); it('should be able to use the back button to navigate back to previous deep link', async () => { await browser.goBack(); - await waitForUrlToBe('/app/dl/home'); + await browser.waitForUrlToBe('/app/dl/home'); await loadingScreenNotShown(); await checkAppVisible('Home'); }); it('should navigate to nested page B when navlink is clicked', async () => { await clickAppLink('DeepPageB'); - await waitForUrlToBe('/app/dl/page-b'); + await browser.waitForUrlToBe('/app/dl/page-b'); await loadingScreenNotShown(); await checkAppVisible('PageB'); }); it('should navigate to Home when navlink is clicked inside the defined category group', async () => { await clickAppLink('Home'); - await waitForUrlToBe('/app/dl/home'); + await browser.waitForUrlToBe('/app/dl/home'); await loadingScreenNotShown(); await checkAppVisible('Home'); }); it('should navigate to nested page B using navigateToApp path', async () => { await clickAppLink('DeepPageB'); - await waitForUrlToBe('/app/dl/page-b'); + await browser.waitForUrlToBe('/app/dl/page-b'); await loadingScreenNotShown(); await checkAppVisible('PageB'); }); it('should navigate to nested page A using navigateToApp deepLinkId', async () => { await clickAppLink('DeepPageAById'); - await waitForUrlToBe('/app/dl/page-a'); + await browser.waitForUrlToBe('/app/dl/page-a'); await loadingScreenNotShown(); await checkAppVisible('PageA'); }); diff --git a/test/plugin_functional/test_suites/core_plugins/application_leave_confirm.ts b/test/plugin_functional/test_suites/core_plugins/application_leave_confirm.ts index 4d0f837108b733..5ec44365f7a64a 100644 --- a/test/plugin_functional/test_suites/core_plugins/application_leave_confirm.ts +++ b/test/plugin_functional/test_suites/core_plugins/application_leave_confirm.ts @@ -6,110 +6,34 @@ * Side Public License, v 1. */ -import expect from '@kbn/expect'; -import url from 'url'; -import { PluginFunctionalProviderContext } from '../../services'; +import type { PluginFunctionalProviderContext } from '../../services'; -const getKibanaUrl = (pathname?: string, search?: string) => - url.format({ - protocol: 'http:', - hostname: process.env.TEST_KIBANA_HOST || 'localhost', - port: process.env.TEST_KIBANA_PORT || '5620', - pathname, - search, - }); - -export default function ({ getService, getPageObjects }: PluginFunctionalProviderContext) { - const PageObjects = getPageObjects(['common', 'header']); +export default function ({ getService, getPageObject }: PluginFunctionalProviderContext) { + const common = getPageObject('common'); const browser = getService('browser'); const appsMenu = getService('appsMenu'); - const log = getService('log'); - const retry = getService('retry'); const testSubjects = getService('testSubjects'); - const config = getService('config'); - - const waitForUrlToBe = async (pathname?: string, search?: string) => { - const expectedUrl = getKibanaUrl(pathname, search); - return await retry.waitFor(`Url to be ${expectedUrl}`, async () => { - const currentUrl = await browser.getCurrentUrl(); - log.debug(`waiting for currentUrl ${currentUrl} to be expectedUrl ${expectedUrl}`); - return currentUrl === expectedUrl; - }); - }; - - const ensureModalOpen = async ( - defaultTryTimeout: number, - attempts: number, - timeMultiplier: number, - action: 'cancel' | 'confirm', - linkText: string = 'home' - ): Promise => { - let isConfirmCancelModalOpenState = false; - - await retry.tryForTime(defaultTryTimeout * timeMultiplier, async () => { - await appsMenu.clickLink(linkText); - isConfirmCancelModalOpenState = await testSubjects.exists('confirmModalTitleText', { - allowHidden: true, - timeout: defaultTryTimeout * timeMultiplier, - }); - }); - if (isConfirmCancelModalOpenState) { - log.debug(`defaultTryTimeout * ${timeMultiplier} is long enough`); - return action === 'cancel' - ? await PageObjects.common.clickCancelOnModal(true, false) - : await PageObjects.common.clickConfirmOnModal(); - } else { - log.debug(`defaultTryTimeout * ${timeMultiplier} is not long enough`); - return await ensureModalOpen( - defaultTryTimeout, - (attempts = attempts > 0 ? attempts - 1 : 0), - (timeMultiplier = timeMultiplier < 10 ? timeMultiplier + 1 : 10), - action, - linkText - ); - } - }; describe('application using leave confirmation', () => { - const defaultTryTimeout = config.get('timeouts.try'); - const attempts = 5; describe('when navigating to another app', () => { - const timeMultiplier = 10; - beforeEach(async () => { - await PageObjects.common.navigateToApp('home'); - }); it('prevents navigation if user click cancel on the confirmation dialog', async () => { - await PageObjects.common.navigateToApp('appleave1'); - await PageObjects.header.waitUntilLoadingHasFinished(); - await waitForUrlToBe('/app/appleave1'); + await common.navigateToApp('appleave1'); + await browser.waitForUrlToBe('/app/appleave1'); - await ensureModalOpen(defaultTryTimeout, attempts, timeMultiplier, 'cancel', 'AppLeave 2'); - await PageObjects.header.waitUntilLoadingHasFinished(); - await retry.waitFor('navigate to appleave1', async () => { - const currentUrl = await browser.getCurrentUrl(); - log.debug(`currentUrl ${currentUrl}`); - return currentUrl.includes('appleave1'); - }); - const currentUrl = await browser.getCurrentUrl(); - expect(currentUrl).to.contain('appleave1'); - await PageObjects.common.navigateToApp('home'); + await appsMenu.clickLink('AppLeave 2', { category: 'kibana' }); + await testSubjects.existOrFail('appLeaveConfirmModal'); + await common.clickCancelOnModal(false); + await browser.waitForUrlToBe('/app/appleave1'); }); it('allows navigation if user click confirm on the confirmation dialog', async () => { - await PageObjects.common.navigateToApp('appleave1'); - await PageObjects.header.waitUntilLoadingHasFinished(); - await waitForUrlToBe('/app/appleave1'); + await common.navigateToApp('appleave1'); + await browser.waitForUrlToBe('/app/appleave1'); - await ensureModalOpen(defaultTryTimeout, attempts, timeMultiplier, 'confirm', 'AppLeave 2'); - await PageObjects.header.waitUntilLoadingHasFinished(); - await retry.waitFor('navigate to appleave1', async () => { - const currentUrl = await browser.getCurrentUrl(); - log.debug(`currentUrl ${currentUrl}`); - return currentUrl.includes('appleave2'); - }); - const currentUrl = await browser.getCurrentUrl(); - expect(currentUrl).to.contain('appleave2'); - await PageObjects.common.navigateToApp('home'); + await appsMenu.clickLink('AppLeave 2', { category: 'kibana' }); + await testSubjects.existOrFail('appLeaveConfirmModal'); + await common.clickConfirmOnModal(); + await browser.waitForUrlToBe('/app/appleave2'); }); }); }); diff --git a/test/plugin_functional/test_suites/core_plugins/applications.ts b/test/plugin_functional/test_suites/core_plugins/applications.ts index b780ef125b71b3..862cb6acfb6df5 100644 --- a/test/plugin_functional/test_suites/core_plugins/applications.ts +++ b/test/plugin_functional/test_suites/core_plugins/applications.ts @@ -6,24 +6,17 @@ * Side Public License, v 1. */ -import url from 'url'; import expect from '@kbn/expect'; import { PluginFunctionalProviderContext } from '../../services'; -export default function ({ getService, getPageObjects }: PluginFunctionalProviderContext) { - const PageObjects = getPageObjects(['common', 'header']); +export default function ({ getService, getPageObject }: PluginFunctionalProviderContext) { + const common = getPageObject('common'); const browser = getService('browser'); const appsMenu = getService('appsMenu'); const testSubjects = getService('testSubjects'); const find = getService('find'); - const retry = getService('retry'); const deployment = getService('deployment'); const esArchiver = getService('esArchiver'); - const log = getService('log'); - - function waitUntilLoadingIsDone() { - return PageObjects.header.waitUntilLoadingHasFinished(); - } const loadingScreenNotShown = async () => expect(await testSubjects.exists('kbnLoadingMessage')).to.be(false); @@ -33,33 +26,6 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide return (await wrapper.getSize()).height; }; - const getKibanaUrl = (pathname?: string, search?: string) => - url.format({ - protocol: 'http:', - hostname: process.env.TEST_KIBANA_HOST || 'localhost', - port: process.env.TEST_KIBANA_PORT || '5620', - pathname, - search, - }); - - async function navigateToAppFromAppsMenu(title: string) { - await retry.try(async () => { - await appsMenu.clickLink(title); - await waitUntilLoadingIsDone(); - }); - } - - /** Use retry logic to make URL assertions less flaky */ - const waitForUrlToBe = (pathname?: string, search?: string) => { - const expectedUrl = getKibanaUrl(pathname, search); - return retry.waitFor(`Url to be ${expectedUrl}`, async () => { - const currentUrl = await browser.getCurrentUrl(); - if (currentUrl !== expectedUrl) - log.debug(`expected url to be ${expectedUrl}, got ${currentUrl}`); - return currentUrl === expectedUrl; - }); - }; - const navigateTo = async (path: string) => await browser.navigateTo(`${deployment.getHostPort()}${path}`); @@ -67,8 +33,8 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide describe.skip('ui applications', function describeIndexTests() { before(async () => { await esArchiver.emptyKibanaIndex(); - await PageObjects.common.navigateToApp('foo'); - await PageObjects.common.dismissBanner(); + await common.navigateToApp('foo'); + await common.dismissBanner(); }); it('starts on home page', async () => { @@ -77,40 +43,37 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide it('redirects and renders correctly regardless of trailing slash', async () => { await navigateTo(`/app/foo`); - await waitForUrlToBe('/app/foo/home'); + await browser.waitForUrlToBe('/app/foo/home'); await testSubjects.existOrFail('fooAppHome'); await navigateTo(`/app/foo/`); - await waitForUrlToBe('/app/foo/home'); + await browser.waitForUrlToBe('/app/foo/home'); await testSubjects.existOrFail('fooAppHome'); }); it('navigates to its own pages', async () => { // Go to page A await testSubjects.click('fooNavPageA'); - await waitForUrlToBe('/app/foo/page-a'); + await browser.waitForUrlToBe('/app/foo/page-a'); await loadingScreenNotShown(); await testSubjects.existOrFail('fooAppPageA'); // Go to home page await testSubjects.click('fooNavHome'); - await waitForUrlToBe('/app/foo/home'); + await browser.waitForUrlToBe('/app/foo/home'); await loadingScreenNotShown(); await testSubjects.existOrFail('fooAppHome'); }); it('can use the back button to navigate within an app', async () => { await browser.goBack(); - await waitForUrlToBe('/app/foo/page-a'); + await browser.waitForUrlToBe('/app/foo/page-a'); await loadingScreenNotShown(); await testSubjects.existOrFail('fooAppPageA'); }); it('navigates to app root when navlink is clicked', async () => { - await testSubjects.click('fooNavHome'); - - navigateToAppFromAppsMenu('Foo'); - - await waitForUrlToBe('/app/foo/home'); + await appsMenu.clickLink('Foo', { category: 'kibana' }); + await browser.waitForUrlToBe('/app/foo/home'); await loadingScreenNotShown(); await testSubjects.existOrFail('fooAppHome'); }); @@ -119,7 +82,7 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide await testSubjects.click('fooNavBarPageB'); await loadingScreenNotShown(); await testSubjects.existOrFail('barAppPageB'); - await waitForUrlToBe('/app/bar/page-b', 'query=here'); + await browser.waitForUrlToBe('/app/bar/page-b?query=here'); }); it('preserves query parameters across apps', async () => { @@ -129,7 +92,7 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide it('can use the back button to navigate back to previous app', async () => { await browser.goBack(); - await waitForUrlToBe('/app/foo/home'); + await browser.waitForUrlToBe('/app/foo/home'); await loadingScreenNotShown(); await testSubjects.existOrFail('fooAppHome'); }); @@ -139,7 +102,7 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide }); it('navigating to chromeless application hides chrome', async () => { - await PageObjects.common.navigateToApp('chromeless'); + await common.navigateToApp('chromeless'); await loadingScreenNotShown(); expect(await testSubjects.exists('headerGlobalNav')).to.be(false); @@ -149,7 +112,7 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide }); it('navigating away from chromeless application shows chrome', async () => { - await PageObjects.common.navigateToApp('foo'); + await common.navigateToApp('foo'); await loadingScreenNotShown(); expect(await testSubjects.exists('headerGlobalNav')).to.be(true); diff --git a/tsconfig.base.json b/tsconfig.base.json index 107b8e1f6ff46c..c367ace533937b 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -622,6 +622,8 @@ "@kbn/data-views-plugin/*": ["src/plugins/data_views/*"], "@kbn/data-visualizer-plugin": ["x-pack/plugins/data_visualizer"], "@kbn/data-visualizer-plugin/*": ["x-pack/plugins/data_visualizer/*"], + "@kbn/dataset-quality-plugin": ["x-pack/plugins/dataset_quality"], + "@kbn/dataset-quality-plugin/*": ["x-pack/plugins/dataset_quality/*"], "@kbn/datemath": ["packages/kbn-datemath"], "@kbn/datemath/*": ["packages/kbn-datemath/*"], "@kbn/deeplinks-analytics": ["packages/deeplinks/analytics"], @@ -700,6 +702,8 @@ "@kbn/encrypted-saved-objects-plugin/*": ["x-pack/plugins/encrypted_saved_objects/*"], "@kbn/enterprise-search-plugin": ["x-pack/plugins/enterprise_search"], "@kbn/enterprise-search-plugin/*": ["x-pack/plugins/enterprise_search/*"], + "@kbn/error-boundary-example-plugin": ["examples/error_boundary"], + "@kbn/error-boundary-example-plugin/*": ["examples/error_boundary/*"], "@kbn/es": ["packages/kbn-es"], "@kbn/es/*": ["packages/kbn-es/*"], "@kbn/es-archiver": ["packages/kbn-es-archiver"], diff --git a/versions.json b/versions.json index b6ac52d0272cef..f904685d127767 100644 --- a/versions.json +++ b/versions.json @@ -8,17 +8,11 @@ "currentMinor": true }, { - "version": "8.11.0", + "version": "8.11.1", "branch": "8.11", "currentMajor": true, "previousMinor": true }, - { - "version": "8.10.5", - "branch": "8.10", - "currentMajor": true, - "previousMinor": true - }, { "version": "7.17.15", "branch": "7.17", diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index 923d0cce0759da..bb14c06a53e362 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -20,6 +20,7 @@ "xpack.csp": "plugins/cloud_security_posture", "xpack.customBranding": "plugins/custom_branding", "xpack.dashboard": "plugins/dashboard_enhanced", + "xpack.datasetQuality": "plugins/dataset_quality", "xpack.discover": "plugins/discover_enhanced", "xpack.crossClusterReplication": "plugins/cross_cluster_replication", "xpack.elasticAssistant": "packages/kbn-elastic-assistant", diff --git a/x-pack/examples/exploratory_view_example/kibana.jsonc b/x-pack/examples/exploratory_view_example/kibana.jsonc index c524e4fda32f74..6cf8fa64983ac8 100644 --- a/x-pack/examples/exploratory_view_example/kibana.jsonc +++ b/x-pack/examples/exploratory_view_example/kibana.jsonc @@ -1,7 +1,7 @@ { "type": "plugin", "id": "@kbn/exploratory-view-example-plugin", - "owner": "@elastic/uptime", + "owner": "@elastic/obs-ux-infra_services-team", "plugin": { "id": "exploratoryViewExample", "server": false, diff --git a/x-pack/packages/kbn-infra-forge/kibana.jsonc b/x-pack/packages/kbn-infra-forge/kibana.jsonc index a66a7336627359..a450d148358a9b 100644 --- a/x-pack/packages/kbn-infra-forge/kibana.jsonc +++ b/x-pack/packages/kbn-infra-forge/kibana.jsonc @@ -1,5 +1,5 @@ { "type": "shared-common", "id": "@kbn/infra-forge", - "owner": "@elastic/actionable-observability" + "owner": "@elastic/obs-ux-management-team" } diff --git a/x-pack/packages/kbn-slo-schema/kibana.jsonc b/x-pack/packages/kbn-slo-schema/kibana.jsonc index 2d12cd108585ce..b4ca324fc112b8 100644 --- a/x-pack/packages/kbn-slo-schema/kibana.jsonc +++ b/x-pack/packages/kbn-slo-schema/kibana.jsonc @@ -1,5 +1,5 @@ { "type": "shared-common", "id": "@kbn/slo-schema", - "owner": "@elastic/actionable-observability", + "owner": "@elastic/obs-ux-management-team" } diff --git a/x-pack/packages/kbn-slo-schema/src/schema/indicators.ts b/x-pack/packages/kbn-slo-schema/src/schema/indicators.ts index f8d795275acc66..c6748eba37968e 100644 --- a/x-pack/packages/kbn-slo-schema/src/schema/indicators.ts +++ b/x-pack/packages/kbn-slo-schema/src/schema/indicators.ts @@ -136,22 +136,29 @@ const timesliceMetricIndicatorSchema = t.type({ ]), }); -const metricCustomValidAggregations = t.keyof({ - sum: true, -}); +const metricCustomDocCountMetric = t.intersection([ + t.type({ + name: t.string, + aggregation: t.literal('doc_count'), + }), + t.partial({ + filter: t.string, + }), +]); + +const metricCustomBasicMetric = t.intersection([ + t.type({ + name: t.string, + aggregation: t.literal('sum'), + field: t.string, + }), + t.partial({ + filter: t.string, + }), +]); + const metricCustomMetricDef = t.type({ - metrics: t.array( - t.intersection([ - t.type({ - name: t.string, - aggregation: metricCustomValidAggregations, - field: t.string, - }), - t.partial({ - filter: t.string, - }), - ]) - ), + metrics: t.array(t.union([metricCustomBasicMetric, metricCustomDocCountMetric])), equation: t.string, }); const metricCustomIndicatorTypeSchema = t.literal('sli.metric.custom'); @@ -267,6 +274,8 @@ export { kqlCustomIndicatorTypeSchema, metricCustomIndicatorSchema, metricCustomIndicatorTypeSchema, + metricCustomDocCountMetric, + metricCustomBasicMetric, timesliceMetricComparatorMapping, timesliceMetricIndicatorSchema, timesliceMetricIndicatorTypeSchema, diff --git a/x-pack/packages/ml/agg_utils/index.ts b/x-pack/packages/ml/agg_utils/index.ts index dd3b694d332c78..6aaf0ff0996462 100644 --- a/x-pack/packages/ml/agg_utils/index.ts +++ b/x-pack/packages/ml/agg_utils/index.ts @@ -18,17 +18,17 @@ export type { NumericHistogramField, } from './src/fetch_histograms_for_fields'; export { isMultiBucketAggregate } from './src/is_multi_bucket_aggregate'; -export { isSignificantTerm } from './src/type_guards'; -export { SIGNIFICANT_TERM_TYPE } from './src/types'; +export { isSignificantItem } from './src/type_guards'; +export { SIGNIFICANT_ITEM_TYPE } from './src/types'; export type { AggCardinality, - SignificantTerm, - SignificantTermGroup, - SignificantTermGroupItem, - SignificantTermGroupHistogram, - SignificantTermHistogram, - SignificantTermHistogramItem, - SignificantTermType, + SignificantItem, + SignificantItemGroup, + SignificantItemGroupItem, + SignificantItemGroupHistogram, + SignificantItemHistogram, + SignificantItemHistogramItem, + SignificantItemType, HistogramField, NumericColumnStats, NumericColumnStatsMap, diff --git a/x-pack/packages/ml/agg_utils/src/type_guards.test.ts b/x-pack/packages/ml/agg_utils/src/type_guards.test.ts index 9cf472abcfb03f..7a2c13a025d0b7 100644 --- a/x-pack/packages/ml/agg_utils/src/type_guards.test.ts +++ b/x-pack/packages/ml/agg_utils/src/type_guards.test.ts @@ -5,15 +5,15 @@ * 2.0. */ -import { isSignificantTerm } from './type_guards'; +import { isSignificantItem } from './type_guards'; -describe('isSignificantTerm', () => { - it('identifies significant terms', () => { - expect(isSignificantTerm({})).toBeFalsy(); - expect(isSignificantTerm({ fieldName: 'response_code' })).toBeFalsy(); - expect(isSignificantTerm({ fieldValue: '500' })).toBeFalsy(); +describe('isSignificantItem', () => { + it('identifies significant items', () => { + expect(isSignificantItem({})).toBeFalsy(); + expect(isSignificantItem({ fieldName: 'response_code' })).toBeFalsy(); + expect(isSignificantItem({ fieldValue: '500' })).toBeFalsy(); expect( - isSignificantTerm({ + isSignificantItem({ key: 'response_code:500', type: 'keyword', fieldName: 'response_code', diff --git a/x-pack/packages/ml/agg_utils/src/type_guards.ts b/x-pack/packages/ml/agg_utils/src/type_guards.ts index 0e33052c8658b3..7d8dbc69b265ea 100644 --- a/x-pack/packages/ml/agg_utils/src/type_guards.ts +++ b/x-pack/packages/ml/agg_utils/src/type_guards.ts @@ -7,17 +7,17 @@ import { isPopulatedObject } from '@kbn/ml-is-populated-object'; -import type { SignificantTerm } from './types'; +import type { SignificantItem } from './types'; /** - * Type guard for a significant term. + * Type guard for a significant item. * Note this is used as a custom type within Log Rate Analysis * for a p-value based variant, not a generic significant terms * aggregation type. * @param arg The unknown type to be evaluated - * @returns whether arg is of type SignificantTerm + * @returns whether arg is of type SignificantItem */ -export function isSignificantTerm(arg: unknown): arg is SignificantTerm { +export function isSignificantItem(arg: unknown): arg is SignificantItem { return isPopulatedObject(arg, [ 'key', 'type', diff --git a/x-pack/packages/ml/agg_utils/src/types.ts b/x-pack/packages/ml/agg_utils/src/types.ts index 026daf861058f9..1f441fce09a6c8 100644 --- a/x-pack/packages/ml/agg_utils/src/types.ts +++ b/x-pack/packages/ml/agg_utils/src/types.ts @@ -88,25 +88,25 @@ export interface HistogramField { } /** - * Enumeration of significant term types. + * Enumeration of significant item types. */ -export const SIGNIFICANT_TERM_TYPE = { +export const SIGNIFICANT_ITEM_TYPE = { KEYWORD: 'keyword', LOG_PATTERN: 'log_pattern', } as const; /** - * Type for significant term type keys. + * Type for significant item type keys. */ -type SignificantTermTypeKeys = keyof typeof SIGNIFICANT_TERM_TYPE; +type SignificantItemTypeKeys = keyof typeof SIGNIFICANT_ITEM_TYPE; /** - * Represents the type of significant term as determined by the SIGNIFICANT_TERM_TYPE enumeration. + * Represents the type of significant item as determined by the SIGNIFICANT_ITEM_TYPE enumeration. */ -export type SignificantTermType = typeof SIGNIFICANT_TERM_TYPE[SignificantTermTypeKeys]; +export type SignificantItemType = typeof SIGNIFICANT_ITEM_TYPE[SignificantItemTypeKeys]; /** - * Represents significant term metadata for a field/value pair. + * Represents significant item metadata for a field/value pair. * This interface is used as a custom type within Log Rate Analysis * for a p-value based variant, not related to the generic * significant terms aggregation type. @@ -114,52 +114,45 @@ export type SignificantTermType = typeof SIGNIFICANT_TERM_TYPE[SignificantTermTy * @interface * @extends FieldValuePair */ -export interface SignificantTerm extends FieldValuePair { - /** The key associated with the significant term. */ +export interface SignificantItem extends FieldValuePair { + /** The key associated with the significant item. */ key: string; - /** The type of the significant term. */ - type: SignificantTermType; + /** The type of the significant item. */ + type: SignificantItemType; - /** The document count for the significant term. */ + /** The document count for the significant item. */ doc_count: number; - /** The background count for the significant term. */ + /** The background count for the significant item. */ bg_count: number; - /** The total document count for all terms. */ + /** The total document count for all items. */ total_doc_count: number; - /** The total background count for all terms. */ + /** The total background count for all items. */ total_bg_count: number; - /** The score associated with the significant term. */ + /** The score associated with the significant item. */ score: number; - /** The p-value for the significant term, or null if not available. */ + /** The p-value for the significant item, or null if not available. */ pValue: number | null; - /** The normalized score for the significant term. */ + /** The normalized score for the significant item. */ normalizedScore: number; - /** An optional histogram of significant term items. */ - histogram?: SignificantTermHistogramItem[]; + /** An optional histogram for the significant item. */ + histogram?: SignificantItemHistogramItem[]; - /** Indicates if the significant term is unique within a group. */ + /** Indicates if the significant item is unique within a group. */ unique?: boolean; } -/** - * Represents a data item in a significant term histogram. - * @interface - */ -export interface SignificantTermHistogramItem { +interface SignificantItemHistogramItemBase { /** The document count for this item in the overall context. */ doc_count_overall: number; - /** The document count for this item in the significant term context. */ - doc_count_significant_term: number; - /** The numeric key associated with this item. */ key: number; @@ -167,37 +160,57 @@ export interface SignificantTermHistogramItem { key_as_string: string; } +/** + * @deprecated since version 2 of internal log rate analysis REST API endpoint + */ +interface SignificantItemHistogramItemV1 extends SignificantItemHistogramItemBase { + /** The document count for this item in the significant term context. */ + doc_count_significant_term: number; +} + +interface SignificantItemHistogramItemV2 extends SignificantItemHistogramItemBase { + /** The document count for this histogram item in the significant item context. */ + doc_count_significant_item: number; +} + +/** + * Represents a data item in a significant term histogram. + */ +export type SignificantItemHistogramItem = + | SignificantItemHistogramItemV1 + | SignificantItemHistogramItemV2; + /** * Represents histogram data for a field/value pair. * @interface */ -export interface SignificantTermHistogram extends FieldValuePair { - /** An array of significant term histogram items. */ - histogram: SignificantTermHistogramItem[]; +export interface SignificantItemHistogram extends FieldValuePair { + /** An array of significant item histogram items. */ + histogram: SignificantItemHistogramItem[]; } /** * Represents histogram data for a group of field/value pairs. * @interface */ -export interface SignificantTermGroupHistogram { +export interface SignificantItemGroupHistogram { /** The identifier for the group. */ id: string; - /** An array of significant term histogram items. */ - histogram: SignificantTermHistogramItem[]; + /** An array of significant item histogram items. */ + histogram: SignificantItemHistogramItem[]; } /** - * Represents an item in a significant term group. + * Represents an item in a significant item group. * @interface */ -export interface SignificantTermGroupItem extends FieldValuePair { - /** The key associated with the significant term. */ +export interface SignificantItemGroupItem extends FieldValuePair { + /** The key associated with the significant item. */ key: string; - /** The type of the significant term. */ - type: SignificantTermType; + /** The type of the significant item. */ + type: SignificantItemType; /** The document count associated with this item. */ docCount: number; @@ -210,15 +223,15 @@ export interface SignificantTermGroupItem extends FieldValuePair { } /** - * Represents a significant term group. + * Represents a significant item group. * @interface */ -export interface SignificantTermGroup { +export interface SignificantItemGroup { /** The identifier for the item. */ id: string; - /** An array of significant term group items. */ - group: SignificantTermGroupItem[]; + /** An array of significant item group items. */ + group: SignificantItemGroupItem[]; /** The document count associated with this item. */ docCount: number; @@ -226,6 +239,6 @@ export interface SignificantTermGroup { /** The p-value for this item, or null if not available. */ pValue: number | null; - /** An optional array of significant term histogram items. */ - histogram?: SignificantTermHistogramItem[]; + /** An optional array of significant item histogram items. */ + histogram?: SignificantItemHistogramItem[]; } diff --git a/x-pack/packages/security-solution/navigation/src/navigation.test.ts b/x-pack/packages/security-solution/navigation/src/navigation.test.ts index ab9ab891aaef8e..a4290563476eac 100644 --- a/x-pack/packages/security-solution/navigation/src/navigation.test.ts +++ b/x-pack/packages/security-solution/navigation/src/navigation.test.ts @@ -7,7 +7,7 @@ import { useGetAppUrl, useNavigateTo } from './navigation'; import { mockGetUrlForApp, mockNavigateToApp, mockNavigateToUrl } from '../mocks/context'; import { renderHook } from '@testing-library/react-hooks'; -import { fireEvent } from '@testing-library/dom'; +import { fireEvent } from '@testing-library/react'; jest.mock('./context'); diff --git a/x-pack/plugins/aiops/common/__mocks__/artificial_logs/final_significant_term_groups.ts b/x-pack/plugins/aiops/common/__mocks__/artificial_logs/final_significant_item_groups.ts similarity index 94% rename from x-pack/plugins/aiops/common/__mocks__/artificial_logs/final_significant_term_groups.ts rename to x-pack/plugins/aiops/common/__mocks__/artificial_logs/final_significant_item_groups.ts index 7166e548449eb0..8f28e49ca3d7ac 100644 --- a/x-pack/plugins/aiops/common/__mocks__/artificial_logs/final_significant_term_groups.ts +++ b/x-pack/plugins/aiops/common/__mocks__/artificial_logs/final_significant_item_groups.ts @@ -5,9 +5,9 @@ * 2.0. */ -import type { SignificantTermGroup } from '@kbn/ml-agg-utils'; +import type { SignificantItemGroup } from '@kbn/ml-agg-utils'; -export const finalSignificantTermGroups: SignificantTermGroup[] = [ +export const finalSignificantItemGroups: SignificantItemGroup[] = [ { docCount: 632, group: [ diff --git a/x-pack/plugins/aiops/common/__mocks__/artificial_logs/final_significant_term_groups_textfield.ts b/x-pack/plugins/aiops/common/__mocks__/artificial_logs/final_significant_item_groups_textfield.ts similarity index 95% rename from x-pack/plugins/aiops/common/__mocks__/artificial_logs/final_significant_term_groups_textfield.ts rename to x-pack/plugins/aiops/common/__mocks__/artificial_logs/final_significant_item_groups_textfield.ts index f959d9408c4184..617a42b930f454 100644 --- a/x-pack/plugins/aiops/common/__mocks__/artificial_logs/final_significant_term_groups_textfield.ts +++ b/x-pack/plugins/aiops/common/__mocks__/artificial_logs/final_significant_item_groups_textfield.ts @@ -5,9 +5,9 @@ * 2.0. */ -import type { SignificantTermGroup } from '@kbn/ml-agg-utils'; +import type { SignificantItemGroup } from '@kbn/ml-agg-utils'; -export const finalSignificantTermGroupsTextfield: SignificantTermGroup[] = [ +export const finalSignificantItemGroupsTextfield: SignificantItemGroup[] = [ { docCount: 636, group: [ diff --git a/x-pack/plugins/aiops/common/__mocks__/artificial_logs/significant_term_groups.ts b/x-pack/plugins/aiops/common/__mocks__/artificial_logs/significant_item_groups.ts similarity index 85% rename from x-pack/plugins/aiops/common/__mocks__/artificial_logs/significant_term_groups.ts rename to x-pack/plugins/aiops/common/__mocks__/artificial_logs/significant_item_groups.ts index 160ce3967cd435..b66738704b2506 100644 --- a/x-pack/plugins/aiops/common/__mocks__/artificial_logs/significant_term_groups.ts +++ b/x-pack/plugins/aiops/common/__mocks__/artificial_logs/significant_item_groups.ts @@ -5,9 +5,9 @@ * 2.0. */ -import type { SignificantTermGroup } from '@kbn/ml-agg-utils'; +import type { SignificantItemGroup } from '@kbn/ml-agg-utils'; -export const significantTermGroups: SignificantTermGroup[] = [ +export const significantItemGroups: SignificantItemGroup[] = [ { id: '2038579476', group: [ diff --git a/x-pack/plugins/aiops/common/__mocks__/artificial_logs/significant_log_patterns.ts b/x-pack/plugins/aiops/common/__mocks__/artificial_logs/significant_log_patterns.ts index ab3ebe02dc5369..9efef525ccd575 100644 --- a/x-pack/plugins/aiops/common/__mocks__/artificial_logs/significant_log_patterns.ts +++ b/x-pack/plugins/aiops/common/__mocks__/artificial_logs/significant_log_patterns.ts @@ -5,9 +5,10 @@ * 2.0. */ -import type { SignificantTerm } from '@kbn/ml-agg-utils'; +import type { SignificantItem } from '@kbn/ml-agg-utils'; -export const significantLogPatterns: SignificantTerm[] = [ +// Named significantLogPatterns since all these items are of type `log_pattern`. +export const significantLogPatterns: SignificantItem[] = [ { bg_count: 0, doc_count: 1266, diff --git a/x-pack/plugins/aiops/common/__mocks__/artificial_logs/significant_terms.ts b/x-pack/plugins/aiops/common/__mocks__/artificial_logs/significant_terms.ts index 4512f5943f4d0a..538bf1bc65eb1a 100644 --- a/x-pack/plugins/aiops/common/__mocks__/artificial_logs/significant_terms.ts +++ b/x-pack/plugins/aiops/common/__mocks__/artificial_logs/significant_terms.ts @@ -5,9 +5,10 @@ * 2.0. */ -import type { SignificantTerm } from '@kbn/ml-agg-utils'; +import type { SignificantItem } from '@kbn/ml-agg-utils'; -export const significantTerms: SignificantTerm[] = [ +// Named significantTerms since all these items are of type `keyword`. +export const significantTerms: SignificantItem[] = [ { key: 'user:Peter', type: 'keyword', diff --git a/x-pack/plugins/aiops/common/__mocks__/farequote/significant_term_groups.ts b/x-pack/plugins/aiops/common/__mocks__/farequote/significant_item_groups.ts similarity index 90% rename from x-pack/plugins/aiops/common/__mocks__/farequote/significant_term_groups.ts rename to x-pack/plugins/aiops/common/__mocks__/farequote/significant_item_groups.ts index 5058f0dbe7e985..95b359f31ea36c 100644 --- a/x-pack/plugins/aiops/common/__mocks__/farequote/significant_term_groups.ts +++ b/x-pack/plugins/aiops/common/__mocks__/farequote/significant_item_groups.ts @@ -5,9 +5,9 @@ * 2.0. */ -import type { SignificantTermGroup } from '@kbn/ml-agg-utils'; +import type { SignificantItemGroup } from '@kbn/ml-agg-utils'; -export const significantTermGroups: SignificantTermGroup[] = [ +export const significantItemGroups: SignificantItemGroup[] = [ { id: 'group-1', group: [ diff --git a/x-pack/plugins/aiops/common/api/index.ts b/x-pack/plugins/aiops/common/api/index.ts index 42f1cfa512900f..cd1852299f265e 100644 --- a/x-pack/plugins/aiops/common/api/index.ts +++ b/x-pack/plugins/aiops/common/api/index.ts @@ -5,14 +5,6 @@ * 2.0. */ -import type { HttpSetup } from '@kbn/core/public'; - -import type { - AiopsLogRateAnalysisSchema, - AiopsLogRateAnalysisApiAction, -} from './log_rate_analysis'; -import { streamReducer } from './stream_reducer'; - export const AIOPS_API_ENDPOINT = { LOG_RATE_ANALYSIS: '/internal/aiops/log_rate_analysis', CATEGORIZATION_FIELD_VALIDATION: '/internal/aiops/categorization_field_validation', @@ -20,12 +12,3 @@ export const AIOPS_API_ENDPOINT = { type AiopsApiEndpointKeys = keyof typeof AIOPS_API_ENDPOINT; export type AiopsApiEndpoint = typeof AIOPS_API_ENDPOINT[AiopsApiEndpointKeys]; - -export interface AiopsApiLogRateAnalysis { - http: HttpSetup; - endpoint: AiopsApiEndpoint; - apiVersion: string; - reducer: typeof streamReducer; - body: AiopsLogRateAnalysisSchema; - actions: AiopsLogRateAnalysisApiAction; -} diff --git a/x-pack/plugins/aiops/common/api/log_rate_analysis/actions.ts b/x-pack/plugins/aiops/common/api/log_rate_analysis/actions.ts index b0ab5ae260955f..bd3afd3152ae5c 100644 --- a/x-pack/plugins/aiops/common/api/log_rate_analysis/actions.ts +++ b/x-pack/plugins/aiops/common/api/log_rate_analysis/actions.ts @@ -6,16 +6,30 @@ */ import type { - SignificantTerm, - SignificantTermHistogram, - SignificantTermGroup, - SignificantTermGroupHistogram, + SignificantItem, + SignificantItemHistogram, + SignificantItemGroup, + SignificantItemGroupHistogram, } from '@kbn/ml-agg-utils'; +import type { AiopsLogRateAnalysisApiVersion as ApiVersion } from './schema'; + export const API_ACTION_NAME = { + /** @since API v2 */ + ADD_SIGNIFICANT_ITEMS: 'add_significant_items', + /** @since API v2 */ + ADD_SIGNIFICANT_ITEMS_HISTOGRAM: 'add_significant_items_histogram', + /** @since API v2 */ + ADD_SIGNIFICANT_ITEMS_GROUP: 'add_significant_items_group', + /** @since API v2 */ + ADD_SIGNIFICANT_ITEMS_GROUP_HISTOGRAM: 'add_significant_items_group_histogram', + /** @deprecated since API v2 */ ADD_SIGNIFICANT_TERMS: 'add_significant_terms', + /** @deprecated since API v2 */ ADD_SIGNIFICANT_TERMS_HISTOGRAM: 'add_significant_terms_histogram', + /** @deprecated since API v2 */ ADD_SIGNIFICANT_TERMS_GROUP: 'add_significant_terms_group', + /** @deprecated since API v2 */ ADD_SIGNIFICANT_TERMS_GROUP_HISTOGRAM: 'add_significant_terms_group_histogram', ADD_ERROR: 'add_error', PING: 'ping', @@ -26,60 +40,108 @@ export const API_ACTION_NAME = { } as const; export type ApiActionName = typeof API_ACTION_NAME[keyof typeof API_ACTION_NAME]; -interface ApiActionAddSignificantTerms { - type: typeof API_ACTION_NAME.ADD_SIGNIFICANT_TERMS; - payload: SignificantTerm[]; -} +interface ApiActionAddSignificantItems { + type: T extends '1' + ? typeof API_ACTION_NAME.ADD_SIGNIFICANT_TERMS + : T extends '2' + ? typeof API_ACTION_NAME.ADD_SIGNIFICANT_ITEMS + : never; + payload: SignificantItem[]; +} + +export function addSignificantItemsAction( + payload: ApiActionAddSignificantItems['payload'], + version: T +): ApiActionAddSignificantItems { + if (version === '1') { + return { + type: API_ACTION_NAME.ADD_SIGNIFICANT_TERMS, + payload, + } as ApiActionAddSignificantItems; + } -export function addSignificantTermsAction( - payload: ApiActionAddSignificantTerms['payload'] -): ApiActionAddSignificantTerms { return { - type: API_ACTION_NAME.ADD_SIGNIFICANT_TERMS, + type: API_ACTION_NAME.ADD_SIGNIFICANT_ITEMS, payload, - }; + } as ApiActionAddSignificantItems; } -interface ApiActionAddSignificantTermsHistogram { - type: typeof API_ACTION_NAME.ADD_SIGNIFICANT_TERMS_HISTOGRAM; - payload: SignificantTermHistogram[]; +interface ApiActionAddSignificantItemsHistogram { + type: T extends '1' + ? typeof API_ACTION_NAME.ADD_SIGNIFICANT_TERMS_HISTOGRAM + : T extends '2' + ? typeof API_ACTION_NAME.ADD_SIGNIFICANT_ITEMS_HISTOGRAM + : never; + payload: SignificantItemHistogram[]; } -export function addSignificantTermsHistogramAction( - payload: ApiActionAddSignificantTermsHistogram['payload'] -): ApiActionAddSignificantTermsHistogram { +export function addSignificantItemsHistogramAction( + payload: ApiActionAddSignificantItemsHistogram['payload'], + version: T +): ApiActionAddSignificantItemsHistogram { + if (version === '1') { + return { + type: API_ACTION_NAME.ADD_SIGNIFICANT_TERMS_HISTOGRAM, + payload, + } as ApiActionAddSignificantItemsHistogram; + } + return { - type: API_ACTION_NAME.ADD_SIGNIFICANT_TERMS_HISTOGRAM, + type: API_ACTION_NAME.ADD_SIGNIFICANT_ITEMS_HISTOGRAM, payload, - }; + } as ApiActionAddSignificantItemsHistogram; } -interface ApiActionAddSignificantTermsGroup { - type: typeof API_ACTION_NAME.ADD_SIGNIFICANT_TERMS_GROUP; - payload: SignificantTermGroup[]; +interface ApiActionAddSignificantItemsGroup { + type: T extends '1' + ? typeof API_ACTION_NAME.ADD_SIGNIFICANT_TERMS_GROUP + : T extends '2' + ? typeof API_ACTION_NAME.ADD_SIGNIFICANT_ITEMS_GROUP + : never; + payload: SignificantItemGroup[]; } -export function addSignificantTermsGroupAction( - payload: ApiActionAddSignificantTermsGroup['payload'] -) { +export function addSignificantItemsGroupAction( + payload: ApiActionAddSignificantItemsGroup['payload'], + version: T +): ApiActionAddSignificantItemsGroup { + if (version === '1') { + return { + type: API_ACTION_NAME.ADD_SIGNIFICANT_TERMS_GROUP, + payload, + } as ApiActionAddSignificantItemsGroup; + } + return { - type: API_ACTION_NAME.ADD_SIGNIFICANT_TERMS_GROUP, + type: API_ACTION_NAME.ADD_SIGNIFICANT_ITEMS_GROUP, payload, - }; + } as ApiActionAddSignificantItemsGroup; } -interface ApiActionAddSignificantTermsGroupHistogram { - type: typeof API_ACTION_NAME.ADD_SIGNIFICANT_TERMS_GROUP_HISTOGRAM; - payload: SignificantTermGroupHistogram[]; +interface ApiActionAddSignificantItemsGroupHistogram { + type: T extends '1' + ? typeof API_ACTION_NAME.ADD_SIGNIFICANT_TERMS_GROUP_HISTOGRAM + : T extends '2' + ? typeof API_ACTION_NAME.ADD_SIGNIFICANT_ITEMS_GROUP_HISTOGRAM + : never; + payload: SignificantItemGroupHistogram[]; } -export function addSignificantTermsGroupHistogramAction( - payload: ApiActionAddSignificantTermsGroupHistogram['payload'] -): ApiActionAddSignificantTermsGroupHistogram { +export function addSignificantItemsGroupHistogramAction( + payload: ApiActionAddSignificantItemsGroupHistogram['payload'], + version: T +): ApiActionAddSignificantItemsGroupHistogram { + if (version === '1') { + return { + type: API_ACTION_NAME.ADD_SIGNIFICANT_TERMS_GROUP_HISTOGRAM, + payload, + } as ApiActionAddSignificantItemsGroupHistogram; + } + return { - type: API_ACTION_NAME.ADD_SIGNIFICANT_TERMS_GROUP_HISTOGRAM, + type: API_ACTION_NAME.ADD_SIGNIFICANT_ITEMS_GROUP_HISTOGRAM, payload, - }; + } as ApiActionAddSignificantItemsGroupHistogram; } interface ApiActionAddError { @@ -148,11 +210,11 @@ export function updateLoadingStateAction( }; } -export type AiopsLogRateAnalysisApiAction = - | ApiActionAddSignificantTerms - | ApiActionAddSignificantTermsGroup - | ApiActionAddSignificantTermsHistogram - | ApiActionAddSignificantTermsGroupHistogram +export type AiopsLogRateAnalysisApiAction = + | ApiActionAddSignificantItems + | ApiActionAddSignificantItemsGroup + | ApiActionAddSignificantItemsHistogram + | ApiActionAddSignificantItemsGroupHistogram | ApiActionAddError | ApiActionPing | ApiActionResetAll diff --git a/x-pack/plugins/aiops/common/api/log_rate_analysis/index.ts b/x-pack/plugins/aiops/common/api/log_rate_analysis/index.ts deleted file mode 100644 index 4687bb8872840b..00000000000000 --- a/x-pack/plugins/aiops/common/api/log_rate_analysis/index.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export { - addSignificantTermsAction, - addSignificantTermsGroupAction, - addSignificantTermsGroupHistogramAction, - addSignificantTermsHistogramAction, - addErrorAction, - pingAction, - resetAllAction, - resetErrorsAction, - resetGroupsAction, - updateLoadingStateAction, - API_ACTION_NAME, -} from './actions'; -export type { AiopsLogRateAnalysisApiAction } from './actions'; - -export { aiopsLogRateAnalysisSchema } from './schema'; -export type { AiopsLogRateAnalysisSchema } from './schema'; diff --git a/x-pack/plugins/aiops/common/api/log_rate_analysis/schema.ts b/x-pack/plugins/aiops/common/api/log_rate_analysis/schema.ts index 499b61c5ba5ca4..a7fbcfd303b553 100644 --- a/x-pack/plugins/aiops/common/api/log_rate_analysis/schema.ts +++ b/x-pack/plugins/aiops/common/api/log_rate_analysis/schema.ts @@ -5,37 +5,17 @@ * 2.0. */ -import { schema, TypeOf } from '@kbn/config-schema'; +import type { AiopsLogRateAnalysisSchemaV1 } from './schema_v1'; +import type { AiopsLogRateAnalysisSchemaV2 } from './schema_v2'; -export const aiopsLogRateAnalysisSchema = schema.object({ - start: schema.number(), - end: schema.number(), - searchQuery: schema.string(), - timeFieldName: schema.string(), - includeFrozen: schema.maybe(schema.boolean()), - grouping: schema.maybe(schema.boolean()), - /** Analysis selection time ranges */ - baselineMin: schema.number(), - baselineMax: schema.number(), - deviationMin: schema.number(), - deviationMax: schema.number(), - /** The index to query for log rate analysis */ - index: schema.string(), - /** Settings to override headers derived compression and flush fix */ - compressResponse: schema.maybe(schema.boolean()), - flushFix: schema.maybe(schema.boolean()), - /** Overrides to skip steps of the analysis with existing data */ - overrides: schema.maybe( - schema.object({ - loaded: schema.maybe(schema.number()), - remainingFieldCandidates: schema.maybe(schema.arrayOf(schema.string())), - // TODO Improve schema - significantTerms: schema.maybe(schema.arrayOf(schema.any())), - regroupOnly: schema.maybe(schema.boolean()), - }) - ), - /** Probability used for the random sampler aggregations */ - sampleProbability: schema.maybe(schema.number()), -}); +export type AiopsLogRateAnalysisApiVersion = '1' | '2'; -export type AiopsLogRateAnalysisSchema = TypeOf; +const LATEST_API_VERSION: AiopsLogRateAnalysisApiVersion = '2'; + +export type AiopsLogRateAnalysisSchema< + T extends AiopsLogRateAnalysisApiVersion = typeof LATEST_API_VERSION +> = T extends '1' + ? AiopsLogRateAnalysisSchemaV1 + : T extends '2' + ? AiopsLogRateAnalysisSchemaV2 + : never; diff --git a/x-pack/plugins/aiops/common/api/log_rate_analysis/schema_v1.ts b/x-pack/plugins/aiops/common/api/log_rate_analysis/schema_v1.ts new file mode 100644 index 00000000000000..e994ecc33dafeb --- /dev/null +++ b/x-pack/plugins/aiops/common/api/log_rate_analysis/schema_v1.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 { schema, TypeOf } from '@kbn/config-schema'; + +export const aiopsLogRateAnalysisSchemaV1 = schema.object({ + start: schema.number(), + end: schema.number(), + searchQuery: schema.string(), + timeFieldName: schema.string(), + includeFrozen: schema.maybe(schema.boolean()), + grouping: schema.maybe(schema.boolean()), + /** Analysis selection time ranges */ + baselineMin: schema.number(), + baselineMax: schema.number(), + deviationMin: schema.number(), + deviationMax: schema.number(), + /** The index to query for log rate analysis */ + index: schema.string(), + /** Settings to override headers derived compression and flush fix */ + compressResponse: schema.maybe(schema.boolean()), + flushFix: schema.maybe(schema.boolean()), + /** Overrides to skip steps of the analysis with existing data */ + overrides: schema.maybe( + schema.object({ + loaded: schema.maybe(schema.number()), + remainingFieldCandidates: schema.maybe(schema.arrayOf(schema.string())), + // TODO Improve schema + significantTerms: schema.maybe(schema.arrayOf(schema.any())), + regroupOnly: schema.maybe(schema.boolean()), + }) + ), + /** Probability used for the random sampler aggregations */ + sampleProbability: schema.maybe(schema.number()), +}); + +export type AiopsLogRateAnalysisSchemaV1 = TypeOf; diff --git a/x-pack/plugins/aiops/common/api/log_rate_analysis/schema_v2.ts b/x-pack/plugins/aiops/common/api/log_rate_analysis/schema_v2.ts new file mode 100644 index 00000000000000..2438c04971e910 --- /dev/null +++ b/x-pack/plugins/aiops/common/api/log_rate_analysis/schema_v2.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 { schema, TypeOf } from '@kbn/config-schema'; + +const significantItem = schema.object({ + key: schema.string(), + type: schema.oneOf([schema.literal('keyword'), schema.literal('log_pattern')]), + fieldName: schema.string(), + fieldValue: schema.oneOf([schema.string(), schema.number()]), + doc_count: schema.number(), + bg_count: schema.number(), + total_doc_count: schema.number(), + total_bg_count: schema.number(), + score: schema.number(), + pValue: schema.nullable(schema.number()), + normalizedScore: schema.number(), + histogram: schema.maybe( + schema.arrayOf( + schema.object({ + doc_count_overall: schema.number(), + doc_count_significant_item: schema.number(), + key: schema.number(), + key_as_string: schema.string(), + }) + ) + ), + unique: schema.maybe(schema.boolean()), +}); + +export const aiopsLogRateAnalysisSchemaV2 = schema.object({ + start: schema.number(), + end: schema.number(), + searchQuery: schema.string(), + timeFieldName: schema.string(), + includeFrozen: schema.maybe(schema.boolean()), + grouping: schema.maybe(schema.boolean()), + /** Analysis selection time ranges */ + baselineMin: schema.number(), + baselineMax: schema.number(), + deviationMin: schema.number(), + deviationMax: schema.number(), + /** The index to query for log rate analysis */ + index: schema.string(), + /** Settings to override headers derived compression and flush fix */ + compressResponse: schema.maybe(schema.boolean()), + flushFix: schema.maybe(schema.boolean()), + /** Overrides to skip steps of the analysis with existing data */ + overrides: schema.maybe( + schema.object({ + loaded: schema.maybe(schema.number()), + remainingFieldCandidates: schema.maybe(schema.arrayOf(schema.string())), + significantItems: schema.maybe(schema.arrayOf(significantItem)), + regroupOnly: schema.maybe(schema.boolean()), + }) + ), + /** Probability used for the random sampler aggregations */ + sampleProbability: schema.maybe(schema.number()), +}); + +export type AiopsLogRateAnalysisSchemaV2 = TypeOf; +export type AiopsLogRateAnalysisSchemaSignificantItem = TypeOf; diff --git a/x-pack/plugins/aiops/common/api/stream_reducer.test.ts b/x-pack/plugins/aiops/common/api/stream_reducer.test.ts index d779ccab356b39..f3dd6cce856c74 100644 --- a/x-pack/plugins/aiops/common/api/stream_reducer.test.ts +++ b/x-pack/plugins/aiops/common/api/stream_reducer.test.ts @@ -6,15 +6,15 @@ */ import { significantTerms } from '../__mocks__/artificial_logs/significant_terms'; -import { finalSignificantTermGroups } from '../__mocks__/artificial_logs/final_significant_term_groups'; +import { finalSignificantItemGroups } from '../__mocks__/artificial_logs/final_significant_item_groups'; import { - addSignificantTermsAction, - addSignificantTermsGroupAction, + addSignificantItemsAction, + addSignificantItemsGroupAction, resetAllAction, resetGroupsAction, updateLoadingStateAction, -} from './log_rate_analysis'; +} from './log_rate_analysis/actions'; import { initialState, streamReducer } from './stream_reducer'; describe('streamReducer', () => { @@ -28,56 +28,59 @@ describe('streamReducer', () => { ccsWarning: true, loaded: 50, loadingState: 'Loaded 50%', - significantTerms: [], - significantTermsGroups: [], + significantItems: [], + significantItemsGroups: [], errors: [], }); }); - it('adds significant term, then resets all state again', () => { + it('adds significant item, then resets all state again', () => { const state1 = streamReducer( initialState, - addSignificantTermsAction([ - { - key: 'the-field-name:the-field-value', - type: 'keyword', - fieldName: 'the-field-name', - fieldValue: 'the-field-value', - doc_count: 10, - bg_count: 100, - total_doc_count: 1000, - total_bg_count: 10000, - score: 0.1, - pValue: 0.01, - normalizedScore: 0.123, - }, - ]) + addSignificantItemsAction( + [ + { + key: 'the-field-name:the-field-value', + type: 'keyword', + fieldName: 'the-field-name', + fieldValue: 'the-field-value', + doc_count: 10, + bg_count: 100, + total_doc_count: 1000, + total_bg_count: 10000, + score: 0.1, + pValue: 0.01, + normalizedScore: 0.123, + }, + ], + '2' + ) ); - expect(state1.significantTerms).toHaveLength(1); + expect(state1.significantItems).toHaveLength(1); const state2 = streamReducer(state1, resetAllAction()); - expect(state2.significantTerms).toHaveLength(0); + expect(state2.significantItems).toHaveLength(0); }); - it('adds significant terms and groups, then resets groups only', () => { - const state1 = streamReducer(initialState, addSignificantTermsAction(significantTerms)); + it('adds significant items and groups, then resets groups only', () => { + const state1 = streamReducer(initialState, addSignificantItemsAction(significantTerms, '2')); - expect(state1.significantTerms).toHaveLength(4); - expect(state1.significantTermsGroups).toHaveLength(0); + expect(state1.significantItems).toHaveLength(4); + expect(state1.significantItemsGroups).toHaveLength(0); const state2 = streamReducer( state1, - addSignificantTermsGroupAction(finalSignificantTermGroups) + addSignificantItemsGroupAction(finalSignificantItemGroups, '2') ); - expect(state2.significantTerms).toHaveLength(4); - expect(state2.significantTermsGroups).toHaveLength(4); + expect(state2.significantItems).toHaveLength(4); + expect(state2.significantItemsGroups).toHaveLength(4); const state3 = streamReducer(state2, resetGroupsAction()); - expect(state3.significantTerms).toHaveLength(4); - expect(state3.significantTermsGroups).toHaveLength(0); + expect(state3.significantItems).toHaveLength(4); + expect(state3.significantItemsGroups).toHaveLength(0); }); }); diff --git a/x-pack/plugins/aiops/common/api/stream_reducer.ts b/x-pack/plugins/aiops/common/api/stream_reducer.ts index 278c3843a2811f..05d3fce52c22e4 100644 --- a/x-pack/plugins/aiops/common/api/stream_reducer.ts +++ b/x-pack/plugins/aiops/common/api/stream_reducer.ts @@ -5,14 +5,14 @@ * 2.0. */ -import type { SignificantTerm, SignificantTermGroup } from '@kbn/ml-agg-utils'; +import type { SignificantItem, SignificantItemGroup } from '@kbn/ml-agg-utils'; -import { API_ACTION_NAME, AiopsLogRateAnalysisApiAction } from './log_rate_analysis'; +import { API_ACTION_NAME, AiopsLogRateAnalysisApiAction } from './log_rate_analysis/actions'; interface StreamState { ccsWarning: boolean; - significantTerms: SignificantTerm[]; - significantTermsGroups: SignificantTermGroup[]; + significantItems: SignificantItem[]; + significantItemsGroups: SignificantItemGroup[]; errors: string[]; loaded: number; loadingState: string; @@ -22,8 +22,8 @@ interface StreamState { export const initialState: StreamState = { ccsWarning: false, - significantTerms: [], - significantTermsGroups: [], + significantItems: [], + significantItemsGroups: [], errors: [], loaded: 0, loadingState: '', @@ -31,17 +31,17 @@ export const initialState: StreamState = { export function streamReducer( state: StreamState, - action: AiopsLogRateAnalysisApiAction | AiopsLogRateAnalysisApiAction[] + action: AiopsLogRateAnalysisApiAction<'2'> | Array> ): StreamState { if (Array.isArray(action)) { return action.reduce(streamReducer, state); } switch (action.type) { - case API_ACTION_NAME.ADD_SIGNIFICANT_TERMS: - return { ...state, significantTerms: [...state.significantTerms, ...action.payload] }; - case API_ACTION_NAME.ADD_SIGNIFICANT_TERMS_HISTOGRAM: - const significantTerms = state.significantTerms.map((cp) => { + case API_ACTION_NAME.ADD_SIGNIFICANT_ITEMS: + return { ...state, significantItems: [...state.significantItems, ...action.payload] }; + case API_ACTION_NAME.ADD_SIGNIFICANT_ITEMS_HISTOGRAM: + const significantItems = state.significantItems.map((cp) => { const cpHistogram = action.payload.find( (h) => h.fieldName === cp.fieldName && h.fieldValue === cp.fieldValue ); @@ -50,24 +50,24 @@ export function streamReducer( } return cp; }); - return { ...state, significantTerms }; - case API_ACTION_NAME.ADD_SIGNIFICANT_TERMS_GROUP: - return { ...state, significantTermsGroups: action.payload }; - case API_ACTION_NAME.ADD_SIGNIFICANT_TERMS_GROUP_HISTOGRAM: - const significantTermsGroups = state.significantTermsGroups.map((cpg) => { + return { ...state, significantItems }; + case API_ACTION_NAME.ADD_SIGNIFICANT_ITEMS_GROUP: + return { ...state, significantItemsGroups: action.payload }; + case API_ACTION_NAME.ADD_SIGNIFICANT_ITEMS_GROUP_HISTOGRAM: + const significantItemsGroups = state.significantItemsGroups.map((cpg) => { const cpHistogram = action.payload.find((h) => h.id === cpg.id); if (cpHistogram) { cpg.histogram = cpHistogram.histogram; } return cpg; }); - return { ...state, significantTermsGroups }; + return { ...state, significantItemsGroups }; case API_ACTION_NAME.ADD_ERROR: return { ...state, errors: [...state.errors, action.payload] }; case API_ACTION_NAME.RESET_ERRORS: return { ...state, errors: [] }; case API_ACTION_NAME.RESET_GROUPS: - return { ...state, significantTermsGroups: [] }; + return { ...state, significantItemsGroups: [] }; case API_ACTION_NAME.RESET_ALL: return initialState; case API_ACTION_NAME.UPDATE_LOADING_STATE: diff --git a/x-pack/plugins/aiops/common/types.ts b/x-pack/plugins/aiops/common/types.ts index 4b26e30c76a72c..67fca0b473549e 100644 --- a/x-pack/plugins/aiops/common/types.ts +++ b/x-pack/plugins/aiops/common/types.ts @@ -5,11 +5,11 @@ * 2.0. */ -import type { SignificantTerm, SignificantTermType, FieldValuePair } from '@kbn/ml-agg-utils'; +import type { SignificantItem, SignificantItemType, FieldValuePair } from '@kbn/ml-agg-utils'; -export interface SignificantTermDuplicateGroup { - keys: Pick; - group: SignificantTerm[]; +export interface SignificantItemDuplicateGroup { + keys: Pick; + group: SignificantItem[]; } export type FieldValuePairCounts = Record>; @@ -31,7 +31,7 @@ export interface FetchFrequentItemSetsResponse { interface SimpleHierarchicalTreeNodeSet extends FieldValuePair { key: string; - type: SignificantTermType; + type: SignificantItemType; docCount: number; pValue: number | null; } diff --git a/x-pack/plugins/aiops/public/application/utils/build_extended_base_filter_criteria.test.ts b/x-pack/plugins/aiops/public/application/utils/build_extended_base_filter_criteria.test.ts index 797f6f1e36a00c..37a984dae490d8 100644 --- a/x-pack/plugins/aiops/public/application/utils/build_extended_base_filter_criteria.test.ts +++ b/x-pack/plugins/aiops/public/application/utils/build_extended_base_filter_criteria.test.ts @@ -5,13 +5,13 @@ * 2.0. */ -import type { SignificantTerm } from '@kbn/ml-agg-utils'; +import type { SignificantItem } from '@kbn/ml-agg-utils'; import type { GroupTableItem } from '../../components/log_rate_analysis_results_table/types'; import { buildExtendedBaseFilterCriteria } from './build_extended_base_filter_criteria'; -const selectedSignificantTermMock: SignificantTerm = { +const selectedSignificantItemMock: SignificantItem = { key: 'meta.cloud.instance_id.keyword:1234', type: 'keyword', doc_count: 53408, @@ -123,13 +123,13 @@ describe('query_utils', () => { ]); }); - it('includes a term filter when including a selectedSignificantTerm', () => { + it('includes a term filter when including a selectedSignificantItem', () => { const baseFilterCriteria = buildExtendedBaseFilterCriteria( '@timestamp', 1640082000012, 1640103600906, { match_all: {} }, - selectedSignificantTermMock + selectedSignificantItemMock ); expect(baseFilterCriteria).toEqual([ @@ -147,13 +147,13 @@ describe('query_utils', () => { ]); }); - it('includes a term filter with must_not when excluding a selectedSignificantTerm', () => { + it('includes a term filter with must_not when excluding a selectedSignificantItem', () => { const baseFilterCriteria = buildExtendedBaseFilterCriteria( '@timestamp', 1640082000012, 1640103600906, { match_all: {} }, - selectedSignificantTermMock, + selectedSignificantItemMock, false ); diff --git a/x-pack/plugins/aiops/public/application/utils/build_extended_base_filter_criteria.ts b/x-pack/plugins/aiops/public/application/utils/build_extended_base_filter_criteria.ts index ee4e707304ff4f..d01951b6b86553 100644 --- a/x-pack/plugins/aiops/public/application/utils/build_extended_base_filter_criteria.ts +++ b/x-pack/plugins/aiops/public/application/utils/build_extended_base_filter_criteria.ts @@ -11,7 +11,7 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { Query } from '@kbn/es-query'; -import { type SignificantTerm, SIGNIFICANT_TERM_TYPE } from '@kbn/ml-agg-utils'; +import { type SignificantItem, SIGNIFICANT_ITEM_TYPE } from '@kbn/ml-agg-utils'; import { buildBaseFilterCriteria } from '@kbn/ml-query-utils'; @@ -30,8 +30,8 @@ export function buildExtendedBaseFilterCriteria( earliestMs?: number, latestMs?: number, query?: Query['query'], - selectedSignificantTerm?: SignificantTerm, - includeSelectedSignificantTerm = true, + selectedSignificantItem?: SignificantItem, + includeSelectedSignificantItem = true, selectedGroup?: GroupTableItem | null ): estypes.QueryDslQueryContainer[] { const filterCriteria = buildBaseFilterCriteria(timeFieldName, earliestMs, latestMs, query); @@ -41,7 +41,7 @@ export function buildExtendedBaseFilterCriteria( const allItems = selectedGroup.groupItemsSortedByUniqueness; for (const item of allItems) { const { fieldName, fieldValue, key, type, docCount } = item; - if (type === SIGNIFICANT_TERM_TYPE.KEYWORD) { + if (type === SIGNIFICANT_ITEM_TYPE.KEYWORD) { groupFilter.push({ term: { [fieldName]: fieldValue } }); } else { groupFilter.push( @@ -57,18 +57,18 @@ export function buildExtendedBaseFilterCriteria( } } - if (includeSelectedSignificantTerm) { - if (selectedSignificantTerm) { - if (selectedSignificantTerm.type === 'keyword') { + if (includeSelectedSignificantItem) { + if (selectedSignificantItem) { + if (selectedSignificantItem.type === 'keyword') { filterCriteria.push({ - term: { [selectedSignificantTerm.fieldName]: selectedSignificantTerm.fieldValue }, + term: { [selectedSignificantItem.fieldName]: selectedSignificantItem.fieldValue }, }); } else { filterCriteria.push( - getCategoryQuery(selectedSignificantTerm.fieldName, [ + getCategoryQuery(selectedSignificantItem.fieldName, [ { - key: `${selectedSignificantTerm.key}`, - count: selectedSignificantTerm.doc_count, + key: `${selectedSignificantItem.key}`, + count: selectedSignificantItem.doc_count, examples: [], }, ]) @@ -77,13 +77,13 @@ export function buildExtendedBaseFilterCriteria( } else if (selectedGroup) { filterCriteria.push(...groupFilter); } - } else if (selectedSignificantTerm && !includeSelectedSignificantTerm) { - if (selectedSignificantTerm.type === 'keyword') { + } else if (selectedSignificantItem && !includeSelectedSignificantItem) { + if (selectedSignificantItem.type === 'keyword') { filterCriteria.push({ bool: { must_not: [ { - term: { [selectedSignificantTerm.fieldName]: selectedSignificantTerm.fieldValue }, + term: { [selectedSignificantItem.fieldName]: selectedSignificantItem.fieldValue }, }, ], }, @@ -92,10 +92,10 @@ export function buildExtendedBaseFilterCriteria( filterCriteria.push({ bool: { must_not: [ - getCategoryQuery(selectedSignificantTerm.fieldName, [ + getCategoryQuery(selectedSignificantItem.fieldName, [ { - key: `${selectedSignificantTerm.key}`, - count: selectedSignificantTerm.doc_count, + key: `${selectedSignificantItem.key}`, + count: selectedSignificantItem.doc_count, examples: [], }, ]), @@ -103,7 +103,7 @@ export function buildExtendedBaseFilterCriteria( }, }); } - } else if (selectedGroup && !includeSelectedSignificantTerm) { + } else if (selectedGroup && !includeSelectedSignificantItem) { filterCriteria.push({ bool: { must_not: [ diff --git a/x-pack/plugins/aiops/public/components/log_categorization/category_table/category_table.tsx b/x-pack/plugins/aiops/public/components/log_categorization/category_table/category_table.tsx index 283bb71e35a766..3796af67f8cd2b 100644 --- a/x-pack/plugins/aiops/public/components/log_categorization/category_table/category_table.tsx +++ b/x-pack/plugins/aiops/public/components/log_categorization/category_table/category_table.tsx @@ -142,7 +142,7 @@ export const CategoryTable: FC = ({ return { doc_count_overall: adjustedDocCount, - doc_count_significant_term: newTerm, + doc_count_significant_item: newTerm, key: catKey, key_as_string: `${catKey}`, }; 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 5e2af3d4cb917a..0770cfea837718 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 @@ -20,7 +20,7 @@ import { type LogRateAnalysisType, type WindowParameters, } from '@kbn/aiops-utils'; -import type { SignificantTerm } from '@kbn/ml-agg-utils'; +import type { SignificantItem } from '@kbn/ml-agg-utils'; import { useData } from '../../../hooks/use_data'; @@ -35,11 +35,11 @@ import { useLogRateAnalysisResultsTableRowContext } from '../../log_rate_analysi const DEFAULT_SEARCH_QUERY = { match_all: {} }; export function getDocumentCountStatsSplitLabel( - significantTerm?: SignificantTerm, + significantItem?: SignificantItem, group?: GroupTableItem ) { - if (significantTerm) { - return `${significantTerm?.fieldName}:${significantTerm?.fieldValue}`; + if (significantItem) { + return `${significantItem?.fieldName}:${significantItem?.fieldValue}`; } else if (group) { return i18n.translate('xpack.aiops.logRateAnalysis.page.documentCountStatsSplitGroupLabel', { defaultMessage: 'Selected group', @@ -94,11 +94,11 @@ export const LogRateAnalysisContent: FC = ({ }, [windowParameters]); const { - currentSelectedSignificantTerm, + currentSelectedSignificantItem, currentSelectedGroup, - setPinnedSignificantTerm, + setPinnedSignificantItem, setPinnedGroup, - setSelectedSignificantTerm, + setSelectedSignificantItem, setSelectedGroup, } = useLogRateAnalysisResultsTableRowContext(); @@ -107,7 +107,7 @@ export const LogRateAnalysisContent: FC = ({ 'log_rate_analysis', esSearchQuery, setGlobalState, - currentSelectedSignificantTerm, + currentSelectedSignificantItem, currentSelectedGroup, undefined, timeRange @@ -132,9 +132,9 @@ export const LogRateAnalysisContent: FC = ({ function clearSelection() { setWindowParameters(undefined); - setPinnedSignificantTerm(null); + setPinnedSignificantItem(null); setPinnedGroup(null); - setSelectedSignificantTerm(null); + setSelectedSignificantItem(null); setSelectedGroup(null); setIsBrushCleared(true); setInitialAnalysisStart(undefined); @@ -148,7 +148,7 @@ export const LogRateAnalysisContent: FC = ({ documentCountStats={documentCountStats} documentCountStatsSplit={documentCountStatsCompare} documentCountStatsSplitLabel={getDocumentCountStatsSplitLabel( - currentSelectedSignificantTerm, + currentSelectedSignificantItem, currentSelectedGroup )} isBrushCleared={isBrushCleared} diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_page.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_page.tsx index c3d53fb999023b..b3c9f256fb2ea6 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_page.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_page.tsx @@ -37,7 +37,7 @@ export const LogRateAnalysisPage: FC = ({ stickyHistogram }) => { const { data: dataService } = useAiopsAppContext(); const { dataView, savedSearch } = useDataSource(); - const { currentSelectedSignificantTerm, currentSelectedGroup } = + const { currentSelectedSignificantItem, currentSelectedGroup } = useLogRateAnalysisResultsTableRowContext(); const [aiopsListState, setAiopsListState] = usePageUrlState( @@ -88,7 +88,7 @@ export const LogRateAnalysisPage: FC = ({ stickyHistogram }) => { 'log_rate_analysis', searchQuery, setGlobalState, - currentSelectedSignificantTerm, + currentSelectedSignificantItem, currentSelectedGroup ); diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_results.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_results.tsx index 97d7201f0140da..457419c8b0501e 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_results.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_results.tsx @@ -31,11 +31,12 @@ import { } from '@kbn/aiops-utils'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import type { SignificantTerm, SignificantTermGroup } from '@kbn/ml-agg-utils'; +import type { SignificantItem, SignificantItemGroup } from '@kbn/ml-agg-utils'; import { useAiopsAppContext } from '../../hooks/use_aiops_app_context'; import { initialState, streamReducer } from '../../../common/api/stream_reducer'; -import type { AiopsApiLogRateAnalysis } from '../../../common/api'; +import type { AiopsLogRateAnalysisSchema } from '../../../common/api/log_rate_analysis/schema'; +import type { AiopsLogRateAnalysisSchemaSignificantItem } from '../../../common/api/log_rate_analysis/schema_v2'; import { AIOPS_TELEMETRY_ID } from '../../../common/constants'; import { getGroupTableItems, @@ -80,9 +81,9 @@ export interface LogRateAnalysisResultsData { /** The type of analysis, whether it's a spike or dip */ analysisType: LogRateAnalysisType; /** Statistically significant field/value items. */ - significantTerms: SignificantTerm[]; + significantItems: SignificantItem[]; /** Statistically significant groups of field/value items. */ - significantTermsGroups: SignificantTermGroup[]; + significantItemsGroups: SignificantItemGroup[]; } /** @@ -145,9 +146,9 @@ export const LogRateAnalysisResults: FC = ({ const [groupResults, setGroupResults] = useState(false); const [groupSkipFields, setGroupSkipFields] = useState([]); const [uniqueFieldNames, setUniqueFieldNames] = useState([]); - const [overrides, setOverrides] = useState< - AiopsApiLogRateAnalysis['body']['overrides'] | undefined - >(undefined); + const [overrides, setOverrides] = useState( + undefined + ); const [shouldStart, setShouldStart] = useState(false); const [toggleIdSelected, setToggleIdSelected] = useState(resultsGroupedOffId); @@ -164,7 +165,9 @@ export const LogRateAnalysisResults: FC = ({ setOverrides({ loaded: 0, remainingFieldCandidates: [], - significantTerms: data.significantTerms.filter((d) => !skippedFields.includes(d.fieldName)), + significantItems: data.significantItems.filter( + (d) => !skippedFields.includes(d.fieldName) + ) as AiopsLogRateAnalysisSchemaSignificantItem[], regroupOnly: true, }); startHandler(true, false); @@ -176,10 +179,10 @@ export const LogRateAnalysisResults: FC = ({ data, isRunning, errors: streamErrors, - } = useFetchStream( + } = useFetchStream, typeof streamReducer>( http, '/internal/aiops/log_rate_analysis', - '1', + '2', { start: earliest, end: latest, @@ -206,10 +209,10 @@ export const LogRateAnalysisResults: FC = ({ { [AIOPS_TELEMETRY_ID.AIOPS_ANALYSIS_RUN_ORIGIN]: embeddingOrigin } ); - const { significantTerms } = data; + const { significantItems } = data; useEffect( - () => setUniqueFieldNames(uniq(significantTerms.map((d) => d.fieldName)).sort()), - [significantTerms] + () => setUniqueFieldNames(uniq(significantItems.map((d) => d.fieldName)).sort()), + [significantItems] ); useEffect(() => { @@ -221,14 +224,18 @@ export const LogRateAnalysisResults: FC = ({ ((Array.isArray(remainingFieldCandidates) && remainingFieldCandidates.length > 0) || groupsMissing) ) { - setOverrides({ loaded, remainingFieldCandidates, significantTerms: data.significantTerms }); + setOverrides({ + loaded, + remainingFieldCandidates, + significantItems: data.significantItems as AiopsLogRateAnalysisSchemaSignificantItem[], + }); } else { setOverrides(undefined); if (onAnalysisCompleted) { onAnalysisCompleted({ analysisType, - significantTerms: data.significantTerms, - significantTermsGroups: data.significantTermsGroups, + significantItems: data.significantItems, + significantItemsGroups: data.significantItemsGroups, }); } } @@ -239,7 +246,7 @@ export const LogRateAnalysisResults: FC = ({ const errors = useMemo(() => [...streamErrors, ...data.errors], [streamErrors, data.errors]); // Start handler clears possibly hovered or pinned - // significant terms on analysis refresh. + // significant items on analysis refresh. function startHandler(continueAnalysis = false, resetGroupButton = true) { if (!continueAnalysis) { setOverrides(undefined); @@ -277,8 +284,8 @@ export const LogRateAnalysisResults: FC = ({ }, []); const groupTableItems = useMemo( - () => getGroupTableItems(data.significantTermsGroups), - [data.significantTermsGroups] + () => getGroupTableItems(data.significantItemsGroups), + [data.significantItemsGroups] ); const shouldRerunAnalysis = useMemo( @@ -288,7 +295,7 @@ export const LogRateAnalysisResults: FC = ({ [currentAnalysisWindowParameters, windowParameters] ); - const showLogRateAnalysisResultsTable = data?.significantTerms.length > 0; + const showLogRateAnalysisResultsTable = data?.significantItems.length > 0; const groupItemCount = groupTableItems.reduce((p, c) => { return p + c.groupItemsSortedByUniqueness.length; }, 0); @@ -475,7 +482,7 @@ export const LogRateAnalysisResults: FC = ({ > {showLogRateAnalysisResultsTable && groupResults ? ( = ({ ) : null} {showLogRateAnalysisResultsTable && !groupResults ? ( { it('transforms groups into table items', () => { - const groupTableItems = getGroupTableItems(finalSignificantTermGroups); + const groupTableItems = getGroupTableItems(finalSignificantItemGroups); expect(groupTableItems).toEqual([ { diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/get_group_table_items.ts b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/get_group_table_items.ts index 11331037de4819..6767b824449460 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/get_group_table_items.ts +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/get_group_table_items.ts @@ -7,14 +7,14 @@ import { sortBy } from 'lodash'; -import type { SignificantTermGroup } from '@kbn/ml-agg-utils'; +import type { SignificantItemGroup } from '@kbn/ml-agg-utils'; import type { GroupTableItem, GroupTableItemGroup } from './types'; export function getGroupTableItems( - significantTermsGroups: SignificantTermGroup[] + significantItemsGroups: SignificantItemGroup[] ): GroupTableItem[] { - const tableItems = significantTermsGroups.map(({ id, group, docCount, histogram, pValue }) => { + const tableItems = significantItemsGroups.map(({ id, group, docCount, histogram, pValue }) => { const sortedGroup = sortBy(group, [(d) => d.fieldName]); const dedupedGroup: GroupTableItemGroup[] = []; diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/get_table_item_as_kql.test.ts b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/get_table_item_as_kql.test.ts index 99b5e9830b08ab..64f4fde55a6607 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/get_table_item_as_kql.test.ts +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/get_table_item_as_kql.test.ts @@ -5,21 +5,21 @@ * 2.0. */ -import { finalSignificantTermGroups } from '../../../common/__mocks__/artificial_logs/final_significant_term_groups'; +import { finalSignificantItemGroups } from '../../../common/__mocks__/artificial_logs/final_significant_item_groups'; import { significantTerms } from '../../../common/__mocks__/artificial_logs/significant_terms'; import { getGroupTableItems } from './get_group_table_items'; import { getTableItemAsKQL } from './get_table_item_as_kql'; describe('getTableItemAsKQL', () => { - it('returns a KQL syntax for a significant term', () => { + it('returns a KQL syntax for a significant item', () => { expect(getTableItemAsKQL(significantTerms[0])).toBe('user:Peter'); expect(getTableItemAsKQL(significantTerms[1])).toBe('response_code:500'); expect(getTableItemAsKQL(significantTerms[2])).toBe('url:home.php'); expect(getTableItemAsKQL(significantTerms[3])).toBe('url:login.php'); }); - it('returns a KQL syntax for a group of significant terms', () => { - const groupTableItems = getGroupTableItems(finalSignificantTermGroups); + it('returns a KQL syntax for a group of significant items', () => { + const groupTableItems = getGroupTableItems(finalSignificantItemGroups); expect(getTableItemAsKQL(groupTableItems[0])).toBe('user:Peter AND url:login.php'); expect(getTableItemAsKQL(groupTableItems[1])).toBe('response_code:500 AND url:home.php'); expect(getTableItemAsKQL(groupTableItems[2])).toBe('url:login.php AND response_code:500'); diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/get_table_item_as_kql.ts b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/get_table_item_as_kql.ts index fb13e3d92173c0..4374da0cec2766 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/get_table_item_as_kql.ts +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/get_table_item_as_kql.ts @@ -6,12 +6,12 @@ */ import { escapeKuery } from '@kbn/es-query'; -import { isSignificantTerm, type SignificantTerm } from '@kbn/ml-agg-utils'; +import { isSignificantItem, type SignificantItem } from '@kbn/ml-agg-utils'; import type { GroupTableItem } from './types'; -export const getTableItemAsKQL = (tableItem: GroupTableItem | SignificantTerm) => { - if (isSignificantTerm(tableItem)) { +export const getTableItemAsKQL = (tableItem: GroupTableItem | SignificantItem) => { + if (isSignificantItem(tableItem)) { return `${escapeKuery(tableItem.fieldName)}:${escapeKuery(String(tableItem.fieldValue))}`; } diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table.tsx index 95f4460c7f9185..d8845145f1ace0 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table.tsx @@ -27,7 +27,7 @@ import type { FieldStatsServices } from '@kbn/unified-field-list/src/components/ import type { DataView } from '@kbn/data-views-plugin/public'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { type SignificantTerm, SIGNIFICANT_TERM_TYPE } from '@kbn/ml-agg-utils'; +import { type SignificantItem, SIGNIFICANT_ITEM_TYPE } from '@kbn/ml-agg-utils'; import type { TimeRange as TimeRangeMs } from '@kbn/ml-date-picker'; import { getCategoryQuery } from '../../../common/api/log_categorization/get_category_query'; @@ -56,7 +56,7 @@ const DEFAULT_SORT_DIRECTION = 'asc'; const TRUNCATE_TEXT_LINES = 3; interface LogRateAnalysisResultsTableProps { - significantTerms: SignificantTerm[]; + significantItems: SignificantItem[]; dataView: DataView; loading: boolean; isExpandedRow?: boolean; @@ -69,7 +69,7 @@ interface LogRateAnalysisResultsTableProps { } export const LogRateAnalysisResultsTable: FC = ({ - significantTerms, + significantItems, dataView, loading, isExpandedRow, @@ -84,16 +84,16 @@ export const LogRateAnalysisResultsTable: FC = const { pinnedGroup, - pinnedSignificantTerm, + pinnedSignificantItem, selectedGroup, - selectedSignificantTerm, - setPinnedSignificantTerm, - setSelectedSignificantTerm, + selectedSignificantItem, + setPinnedSignificantItem, + setSelectedSignificantItem, } = useLogRateAnalysisResultsTableRowContext(); const [pageIndex, setPageIndex] = useState(0); const [pageSize, setPageSize] = useState(10); - const [sortField, setSortField] = useState(DEFAULT_SORT_FIELD); + const [sortField, setSortField] = useState(DEFAULT_SORT_FIELD); const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>(DEFAULT_SORT_DIRECTION); const { data, uiSettings, fieldFormats, charts } = useAiopsAppContext(); @@ -112,7 +112,7 @@ export const LogRateAnalysisResultsTable: FC = const viewInDiscoverAction = useViewInDiscoverAction(dataViewId); const viewInLogPatternAnalysisAction = useViewInLogPatternAnalysisAction(dataViewId); - const columns: Array> = [ + const columns: Array> = [ { 'data-test-subj': 'aiopsLogRateAnalysisResultsTableColumnFieldName', field: 'fieldName', @@ -121,7 +121,7 @@ export const LogRateAnalysisResultsTable: FC = }), render: (_, { fieldName, fieldValue, key, type, doc_count: count }) => { const dslQuery = - type === SIGNIFICANT_TERM_TYPE.KEYWORD + type === SIGNIFICANT_ITEM_TYPE.KEYWORD ? searchQuery : getCategoryQuery(fieldName, [ { @@ -132,17 +132,17 @@ export const LogRateAnalysisResultsTable: FC = ]); return ( <> - {type === SIGNIFICANT_TERM_TYPE.KEYWORD && ( + {type === SIGNIFICANT_ITEM_TYPE.KEYWORD && ( )} - {type === SIGNIFICANT_TERM_TYPE.LOG_PATTERN && ( + {type === SIGNIFICANT_ITEM_TYPE.LOG_PATTERN && ( = const { pagination, pageOfItems, sorting } = useMemo(() => { const pageStart = pageIndex * pageSize; - const itemCount = significantTerms?.length ?? 0; + const itemCount = significantItems?.length ?? 0; - let items: SignificantTerm[] = significantTerms ?? []; + let items: SignificantItem[] = significantItems ?? []; const sortIteratees = [ - (item: SignificantTerm) => { + (item: SignificantItem) => { if (item && typeof item[sortField] === 'string') { // @ts-ignore Object is possibly null or undefined return item[sortField].toLowerCase(); @@ -368,11 +368,11 @@ export const LogRateAnalysisResultsTable: FC = // Only if the table is sorted by p-value, add a secondary sort by doc count. if (sortField === 'pValue') { - sortIteratees.push((item: SignificantTerm) => item.doc_count); + sortIteratees.push((item: SignificantItem) => item.doc_count); sortDirections.push(sortDirection); } - items = orderBy(significantTerms, sortIteratees, sortDirections); + items = orderBy(significantItems, sortIteratees, sortDirections); return { pageOfItems: items.slice(pageStart, pageStart + pageSize), @@ -389,59 +389,59 @@ export const LogRateAnalysisResultsTable: FC = }, }, }; - }, [pageIndex, pageSize, sortField, sortDirection, significantTerms]); + }, [pageIndex, pageSize, sortField, sortDirection, significantItems]); useEffect(() => { // If no row is hovered or pinned or the user switched to a new page, // fall back to set the first row into a hovered state to make the // main document count chart show a comparison view by default. if ( - (selectedSignificantTerm === null || - !pageOfItems.some((item) => isEqual(item, selectedSignificantTerm))) && - pinnedSignificantTerm === null && + (selectedSignificantItem === null || + !pageOfItems.some((item) => isEqual(item, selectedSignificantItem))) && + pinnedSignificantItem === null && pageOfItems.length > 0 && selectedGroup === null && pinnedGroup === null ) { - setSelectedSignificantTerm(pageOfItems[0]); + setSelectedSignificantItem(pageOfItems[0]); } // If a user switched pages and a pinned row is no longer visible // on the current page, set the status of pinned rows back to `null`. if ( - pinnedSignificantTerm !== null && - !pageOfItems.some((item) => isEqual(item, pinnedSignificantTerm)) && + pinnedSignificantItem !== null && + !pageOfItems.some((item) => isEqual(item, pinnedSignificantItem)) && selectedGroup === null && pinnedGroup === null ) { - setPinnedSignificantTerm(null); + setPinnedSignificantItem(null); } }, [ selectedGroup, - selectedSignificantTerm, - setSelectedSignificantTerm, - setPinnedSignificantTerm, + selectedSignificantItem, + setSelectedSignificantItem, + setPinnedSignificantItem, pageOfItems, pinnedGroup, - pinnedSignificantTerm, + pinnedSignificantItem, ]); // When the analysis results table unmounts, // make sure to reset any hovered or pinned rows. useEffect( () => () => { - setSelectedSignificantTerm(null); - setPinnedSignificantTerm(null); + setSelectedSignificantItem(null); + setPinnedSignificantItem(null); }, // eslint-disable-next-line react-hooks/exhaustive-deps [] ); - const getRowStyle = (significantTerm: SignificantTerm) => { + const getRowStyle = (significantItem: SignificantItem) => { if ( - pinnedSignificantTerm && - pinnedSignificantTerm.fieldName === significantTerm.fieldName && - pinnedSignificantTerm.fieldValue === significantTerm.fieldValue + pinnedSignificantItem && + pinnedSignificantItem.fieldName === significantItem.fieldName && + pinnedSignificantItem.fieldValue === significantItem.fieldValue ) { return { backgroundColor: primaryBackgroundColor, @@ -449,9 +449,9 @@ export const LogRateAnalysisResultsTable: FC = } if ( - selectedSignificantTerm && - selectedSignificantTerm.fieldName === significantTerm.fieldName && - selectedSignificantTerm.fieldValue === significantTerm.fieldValue + selectedSignificantItem && + selectedSignificantItem.fieldName === significantItem.fieldName && + selectedSignificantItem.fieldValue === significantItem.fieldValue ) { return { backgroundColor: euiTheme.euiColorLightestShade, @@ -479,29 +479,29 @@ export const LogRateAnalysisResultsTable: FC = onChange={onChange} pagination={pagination.totalItemCount > pagination.pageSize ? pagination : undefined} loading={false} - sorting={sorting as EuiTableSortingType} - rowProps={(significantTerm) => { + sorting={sorting as EuiTableSortingType} + rowProps={(significantItem) => { return { - 'data-test-subj': `aiopsLogRateAnalysisResultsTableRow row-${significantTerm.fieldName}-${significantTerm.fieldValue}`, + 'data-test-subj': `aiopsLogRateAnalysisResultsTableRow row-${significantItem.fieldName}-${significantItem.fieldValue}`, onClick: () => { if ( - significantTerm.fieldName === pinnedSignificantTerm?.fieldName && - significantTerm.fieldValue === pinnedSignificantTerm?.fieldValue + significantItem.fieldName === pinnedSignificantItem?.fieldName && + significantItem.fieldValue === pinnedSignificantItem?.fieldValue ) { - setPinnedSignificantTerm(null); + setPinnedSignificantItem(null); } else { - setPinnedSignificantTerm(significantTerm); + setPinnedSignificantItem(significantItem); } }, onMouseEnter: () => { - if (pinnedSignificantTerm === null) { - setSelectedSignificantTerm(significantTerm); + if (pinnedSignificantItem === null) { + setSelectedSignificantItem(significantItem); } }, onMouseLeave: () => { - setSelectedSignificantTerm(null); + setSelectedSignificantItem(null); }, - style: getRowStyle(significantTerm), + style: getRowStyle(significantItem), }; }} /> diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table_groups.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table_groups.tsx index 9f45c79b0746d7..f6961e49c2c789 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table_groups.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table_groups.tsx @@ -28,7 +28,7 @@ import { import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import type { SignificantTerm } from '@kbn/ml-agg-utils'; +import type { SignificantItem } from '@kbn/ml-agg-utils'; import type { TimeRange as TimeRangeMs } from '@kbn/ml-date-picker'; import type { DataView } from '@kbn/data-views-plugin/public'; @@ -53,7 +53,7 @@ const DEFAULT_SORT_FIELD = 'pValue'; const DEFAULT_SORT_DIRECTION = 'asc'; interface LogRateAnalysisResultsTableProps { - significantTerms: SignificantTerm[]; + significantItems: SignificantItem[]; groupTableItems: GroupTableItem[]; loading: boolean; searchQuery: estypes.QueryDslQueryContainer; @@ -66,7 +66,7 @@ interface LogRateAnalysisResultsTableProps { } export const LogRateAnalysisResultsGroupsTable: FC = ({ - significantTerms, + significantItems, groupTableItems, loading, dataView, @@ -98,9 +98,9 @@ export const LogRateAnalysisResultsGroupsTable: FC( + significantItems={item.groupItemsSortedByUniqueness.reduce( (p, groupItem) => { - const st = significantTerms.find( + const st = significantItems.find( (d) => d.fieldName === groupItem.fieldName && d.fieldValue === groupItem.fieldValue ); @@ -139,7 +139,11 @@ export const LogRateAnalysisResultsGroupsTable: FC - Expand rows + + {i18n.translate('xpack.aiops.logRateAnalysis.resultsTable.expandRowsLabel', { + defaultMessage: 'Expand rows', + })} + ), render: (item: GroupTableItem) => ( diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table_row_provider.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table_row_provider.tsx index f4d6c77c9756f9..a7b3398e4e7ccf 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table_row_provider.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/log_rate_analysis_results_table_row_provider.tsx @@ -15,23 +15,23 @@ import React, { type SetStateAction, } from 'react'; -import type { SignificantTerm } from '@kbn/ml-agg-utils'; +import type { SignificantItem } from '@kbn/ml-agg-utils'; import type { GroupTableItem } from './types'; -type SignificantTermOrNull = SignificantTerm | null; +type SignificantItemOrNull = SignificantItem | null; type GroupOrNull = GroupTableItem | null; interface LogRateAnalysisResultsTableRow { - pinnedSignificantTerm: SignificantTermOrNull; - setPinnedSignificantTerm: Dispatch>; + pinnedSignificantItem: SignificantItemOrNull; + setPinnedSignificantItem: Dispatch>; pinnedGroup: GroupOrNull; setPinnedGroup: Dispatch>; - selectedSignificantTerm: SignificantTermOrNull; - setSelectedSignificantTerm: Dispatch>; + selectedSignificantItem: SignificantItemOrNull; + setSelectedSignificantItem: Dispatch>; selectedGroup: GroupOrNull; setSelectedGroup: Dispatch>; - currentSelectedSignificantTerm?: SignificantTerm; + currentSelectedSignificantItem?: SignificantItem; currentSelectedGroup?: GroupTableItem; clearAllRowState: () => void; } @@ -42,20 +42,20 @@ export const logRateAnalysisResultsTableRowContext = createContext< export const LogRateAnalysisResultsTableRowStateProvider: FC = ({ children }) => { // State that will be shared with all components - const [pinnedSignificantTerm, setPinnedSignificantTerm] = useState(null); + const [pinnedSignificantItem, setPinnedSignificantItem] = useState(null); const [pinnedGroup, setPinnedGroup] = useState(null); - const [selectedSignificantTerm, setSelectedSignificantTerm] = - useState(null); + const [selectedSignificantItem, setSelectedSignificantItem] = + useState(null); const [selectedGroup, setSelectedGroup] = useState(null); // If a row is pinned, still overrule with a potentially hovered row. - const currentSelectedSignificantTerm = useMemo(() => { - if (selectedSignificantTerm) { - return selectedSignificantTerm; - } else if (pinnedSignificantTerm) { - return pinnedSignificantTerm; + const currentSelectedSignificantItem = useMemo(() => { + if (selectedSignificantItem) { + return selectedSignificantItem; + } else if (pinnedSignificantItem) { + return pinnedSignificantItem; } - }, [pinnedSignificantTerm, selectedSignificantTerm]); + }, [pinnedSignificantItem, selectedSignificantItem]); // If a group is pinned, still overrule with a potentially hovered group. const currentSelectedGroup = useMemo(() => { @@ -68,33 +68,33 @@ export const LogRateAnalysisResultsTableRowStateProvider: FC = ({ children }) => const contextValue: LogRateAnalysisResultsTableRow = useMemo( () => ({ - pinnedSignificantTerm, - setPinnedSignificantTerm, + pinnedSignificantItem, + setPinnedSignificantItem, pinnedGroup, setPinnedGroup, - selectedSignificantTerm, - setSelectedSignificantTerm, + selectedSignificantItem, + setSelectedSignificantItem, selectedGroup, setSelectedGroup, - currentSelectedSignificantTerm, + currentSelectedSignificantItem, currentSelectedGroup, clearAllRowState: () => { - setPinnedSignificantTerm(null); + setPinnedSignificantItem(null); setPinnedGroup(null); - setSelectedSignificantTerm(null); + setSelectedSignificantItem(null); setSelectedGroup(null); }, }), [ - pinnedSignificantTerm, - setPinnedSignificantTerm, + pinnedSignificantItem, + setPinnedSignificantItem, pinnedGroup, setPinnedGroup, - selectedSignificantTerm, - setSelectedSignificantTerm, + selectedSignificantItem, + setSelectedSignificantItem, selectedGroup, setSelectedGroup, - currentSelectedSignificantTerm, + currentSelectedSignificantItem, currentSelectedGroup, ] ); diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/types.ts b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/types.ts index 66a8a7fe5ab00d..400e4534b54f13 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/types.ts +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/types.ts @@ -7,10 +7,10 @@ import type { EuiTableActionsColumnType } from '@elastic/eui'; -import type { SignificantTerm, SignificantTermGroupItem } from '@kbn/ml-agg-utils'; +import type { SignificantItem, SignificantItemGroupItem } from '@kbn/ml-agg-utils'; export type GroupTableItemGroup = Pick< - SignificantTermGroupItem, + SignificantItemGroupItem, 'key' | 'type' | 'fieldName' | 'fieldValue' | 'docCount' | 'pValue' | 'duplicate' >; @@ -20,9 +20,9 @@ export interface GroupTableItem { pValue: number | null; uniqueItemsCount: number; groupItemsSortedByUniqueness: GroupTableItemGroup[]; - histogram: SignificantTerm['histogram']; + histogram: SignificantItem['histogram']; } export type TableItemAction = EuiTableActionsColumnType< - SignificantTerm | GroupTableItem + SignificantItem | GroupTableItem >['actions'][number]; diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_copy_to_clipboard_action.test.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_copy_to_clipboard_action.test.tsx index 0984c76a4b1708..52ffafd29bcc5d 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_copy_to_clipboard_action.test.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_copy_to_clipboard_action.test.tsx @@ -10,9 +10,9 @@ import userEvent from '@testing-library/user-event'; import { render, act } from '@testing-library/react'; import { renderHook } from '@testing-library/react-hooks'; -import type { SignificantTerm } from '@kbn/ml-agg-utils'; +import type { SignificantItem } from '@kbn/ml-agg-utils'; -import { finalSignificantTermGroups } from '../../../common/__mocks__/artificial_logs/final_significant_term_groups'; +import { finalSignificantItemGroups } from '../../../common/__mocks__/artificial_logs/final_significant_item_groups'; import { significantTerms } from '../../../common/__mocks__/artificial_logs/significant_terms'; import { getGroupTableItems } from './get_group_table_items'; @@ -20,14 +20,14 @@ import { useCopyToClipboardAction } from './use_copy_to_clipboard_action'; import type { GroupTableItem } from './types'; interface Action { - render: (tableItem: SignificantTerm | GroupTableItem) => ReactElement; + render: (tableItem: SignificantItem | GroupTableItem) => ReactElement; } const execCommandMock = (global.document.execCommand = jest.fn()); const warn = jest.spyOn(console, 'warn').mockImplementation(() => {}); describe('useCopyToClipboardAction', () => { - it('renders the action for a single significant term', async () => { + it('renders the action for a single significant item', async () => { execCommandMock.mockImplementationOnce(() => true); const { result } = renderHook(() => useCopyToClipboardAction()); const { findByText, getByTestId } = render( @@ -57,7 +57,7 @@ describe('useCopyToClipboardAction', () => { it('renders the action for a group of items', async () => { execCommandMock.mockImplementationOnce(() => true); - const groupTableItems = getGroupTableItems(finalSignificantTermGroups); + const groupTableItems = getGroupTableItems(finalSignificantItemGroups); const { result } = renderHook(useCopyToClipboardAction); const { findByText, getByText } = render((result.current as Action).render(groupTableItems[0])); diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_copy_to_clipboard_action.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_copy_to_clipboard_action.tsx index 87b5239843940b..b7dc328f1a4684 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_copy_to_clipboard_action.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_copy_to_clipboard_action.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { EuiCopy, EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { isSignificantTerm, type SignificantTerm } from '@kbn/ml-agg-utils'; +import { isSignificantItem, type SignificantItem } from '@kbn/ml-agg-utils'; import { TableActionButton } from './table_action_button'; import { getTableItemAsKQL } from './get_table_item_as_kql'; @@ -23,8 +23,8 @@ const copyToClipboardButtonLabel = i18n.translate( } ); -const copyToClipboardSignificantTermMessage = i18n.translate( - 'xpack.aiops.logRateAnalysis.resultsTable.linksMenu.copyToClipboardSignificantTermMessage', +const copyToClipboardSignificantItemMessage = i18n.translate( + 'xpack.aiops.logRateAnalysis.resultsTable.linksMenu.copyToClipboardSignificantItemMessage', { defaultMessage: 'Copy field/value pair as KQL syntax to clipboard', } @@ -38,9 +38,9 @@ const copyToClipboardGroupMessage = i18n.translate( ); export const useCopyToClipboardAction = (): TableItemAction => ({ - render: (tableItem: SignificantTerm | GroupTableItem) => { - const message = isSignificantTerm(tableItem) - ? copyToClipboardSignificantTermMessage + render: (tableItem: SignificantItem | GroupTableItem) => { + const message = isSignificantItem(tableItem) + ? copyToClipboardSignificantItemMessage : copyToClipboardGroupMessage; return ( diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_view_in_discover_action.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_view_in_discover_action.tsx index 1547daba0017ec..7f467ded4c24b7 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_view_in_discover_action.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_view_in_discover_action.tsx @@ -8,7 +8,7 @@ import React, { useMemo } from 'react'; import { i18n } from '@kbn/i18n'; -import type { SignificantTerm } from '@kbn/ml-agg-utils'; +import type { SignificantItem } from '@kbn/ml-agg-utils'; import { SEARCH_QUERY_LANGUAGE } from '@kbn/ml-query-utils'; import { useAiopsAppContext } from '../../hooks/use_aiops_app_context'; @@ -65,7 +65,7 @@ export const useViewInDiscoverAction = (dataViewId?: string): TableItemAction => } }, [application.capabilities.discover?.show, dataViewId, discoverLocator]); - const generateDiscoverUrl = async (groupTableItem: GroupTableItem | SignificantTerm) => { + const generateDiscoverUrl = async (groupTableItem: GroupTableItem | SignificantItem) => { if (discoverLocator !== undefined) { const url = await discoverLocator.getRedirectUrl({ indexPatternId: dataViewId, @@ -82,7 +82,7 @@ export const useViewInDiscoverAction = (dataViewId?: string): TableItemAction => }; return { - render: (tableItem: SignificantTerm | GroupTableItem) => { + render: (tableItem: SignificantItem | GroupTableItem) => { const tooltipText = discoverUrlError ? discoverUrlError : viewInDiscoverMessage; const clickHandler = async () => { diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_view_in_log_pattern_analysis_action.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_view_in_log_pattern_analysis_action.tsx index ba25db2b76aac1..86f468e2073891 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_view_in_log_pattern_analysis_action.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_view_in_log_pattern_analysis_action.tsx @@ -10,7 +10,7 @@ import React, { useMemo } from 'react'; import { SerializableRecord } from '@kbn/utility-types'; import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; -import { isSignificantTerm, type SignificantTerm, SIGNIFICANT_TERM_TYPE } from '@kbn/ml-agg-utils'; +import { isSignificantItem, type SignificantItem, SIGNIFICANT_ITEM_TYPE } from '@kbn/ml-agg-utils'; import { SEARCH_QUERY_LANGUAGE } from '@kbn/ml-query-utils'; import { useAiopsAppContext } from '../../hooks/use_aiops_app_context'; @@ -19,8 +19,8 @@ import { TableActionButton } from './table_action_button'; import { getTableItemAsKQL } from './get_table_item_as_kql'; import type { GroupTableItem, TableItemAction } from './types'; -const isLogPattern = (tableItem: SignificantTerm | GroupTableItem) => - isSignificantTerm(tableItem) && tableItem.type === SIGNIFICANT_TERM_TYPE.LOG_PATTERN; +const isLogPattern = (tableItem: SignificantItem | GroupTableItem) => + isSignificantItem(tableItem) && tableItem.type === SIGNIFICANT_ITEM_TYPE.LOG_PATTERN; const viewInLogPatternAnalysisMessage = i18n.translate( 'xpack.aiops.logRateAnalysis.resultsTable.linksMenu.viewInLogPatternAnalysis', @@ -35,7 +35,7 @@ export const useViewInLogPatternAnalysisAction = (dataViewId?: string): TableIte const mlLocator = useMemo(() => share.url.locators.get('ML_APP_LOCATOR'), [share.url.locators]); const generateLogPatternAnalysisUrl = async ( - groupTableItem: GroupTableItem | SignificantTerm + groupTableItem: GroupTableItem | SignificantItem ) => { if (mlLocator !== undefined) { const searchString = getTableItemAsKQL(groupTableItem); @@ -85,7 +85,7 @@ export const useViewInLogPatternAnalysisAction = (dataViewId?: string): TableIte }, [dataViewId, mlLocator]); return { - render: (tableItem: SignificantTerm | GroupTableItem) => { + render: (tableItem: SignificantItem | GroupTableItem) => { const message = logPatternAnalysisUrlError ? logPatternAnalysisUrlError : viewInLogPatternAnalysisMessage; diff --git a/x-pack/plugins/aiops/public/components/mini_histogram/mini_histogram.tsx b/x-pack/plugins/aiops/public/components/mini_histogram/mini_histogram.tsx index 1e65c797cacbbf..6f1564cb6f86ce 100644 --- a/x-pack/plugins/aiops/public/components/mini_histogram/mini_histogram.tsx +++ b/x-pack/plugins/aiops/public/components/mini_histogram/mini_histogram.tsx @@ -20,14 +20,14 @@ import { import { EuiLoadingChart, EuiTextColor } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -import type { SignificantTermHistogramItem } from '@kbn/ml-agg-utils'; +import type { SignificantItemHistogramItem } from '@kbn/ml-agg-utils'; import { i18n } from '@kbn/i18n'; import { useAiopsAppContext } from '../../hooks/use_aiops_app_context'; import { useEuiTheme } from '../../hooks/use_eui_theme'; interface MiniHistogramProps { - chartData?: SignificantTermHistogramItem[]; + chartData?: SignificantItemHistogramItem[]; isLoading: boolean; label: string; /** Optional color override for the default bar color for charts */ @@ -126,7 +126,7 @@ export const MiniHistogram: FC = ({ xScaleType={ScaleType.Time} yScaleType={ScaleType.Linear} xAccessor={'key'} - yAccessors={['doc_count_significant_term']} + yAccessors={['doc_count_significant_item']} data={chartData} stackAccessors={[0]} color={barHighlightColor} diff --git a/x-pack/plugins/aiops/public/get_document_stats.ts b/x-pack/plugins/aiops/public/get_document_stats.ts index 9c5a53c8870e78..d4933d103eb7e0 100644 --- a/x-pack/plugins/aiops/public/get_document_stats.ts +++ b/x-pack/plugins/aiops/public/get_document_stats.ts @@ -10,7 +10,7 @@ import { each, get } from 'lodash'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { isPopulatedObject } from '@kbn/ml-is-populated-object'; -import type { SignificantTerm } from '@kbn/ml-agg-utils'; +import type { SignificantItem } from '@kbn/ml-agg-utils'; import type { Query } from '@kbn/es-query'; import type { RandomSamplerWrapper } from '@kbn/ml-random-sampler-utils'; @@ -34,8 +34,8 @@ export interface DocumentStatsSearchStrategyParams { timeFieldName?: string; runtimeFieldMap?: estypes.MappingRuntimeFields; fieldsToFetch?: string[]; - selectedSignificantTerm?: SignificantTerm; - includeSelectedSignificantTerm?: boolean; + selectedSignificantItem?: SignificantItem; + includeSelectedSignificantItem?: boolean; selectedGroup?: GroupTableItem | null; trackTotalHits?: boolean; } @@ -54,8 +54,8 @@ export const getDocumentCountStatsRequest = ( searchQuery, intervalMs, fieldsToFetch, - selectedSignificantTerm, - includeSelectedSignificantTerm, + selectedSignificantItem, + includeSelectedSignificantItem, selectedGroup, trackTotalHits, } = params; @@ -66,8 +66,8 @@ export const getDocumentCountStatsRequest = ( earliestMs, latestMs, searchQuery, - selectedSignificantTerm, - includeSelectedSignificantTerm, + selectedSignificantItem, + includeSelectedSignificantItem, selectedGroup ); diff --git a/x-pack/plugins/aiops/public/hooks/use_data.ts b/x-pack/plugins/aiops/public/hooks/use_data.ts index d44eb1661db348..934a470588960b 100644 --- a/x-pack/plugins/aiops/public/hooks/use_data.ts +++ b/x-pack/plugins/aiops/public/hooks/use_data.ts @@ -11,7 +11,7 @@ import type { Moment } from 'moment'; import { useExecutionContext } from '@kbn/kibana-react-plugin/public'; import type { DataView } from '@kbn/data-views-plugin/public'; -import type { SignificantTerm } from '@kbn/ml-agg-utils'; +import type { SignificantItem } from '@kbn/ml-agg-utils'; import type { Dictionary } from '@kbn/ml-url-state'; import { mlTimefilterRefresh$, useTimefilter } from '@kbn/ml-date-picker'; @@ -34,7 +34,7 @@ export const useData = ( contextId: string, searchQuery: estypes.QueryDslQueryContainer, onUpdate?: (params: Dictionary) => void, - selectedSignificantTerm?: SignificantTerm, + selectedSignificantItem?: SignificantItem, selectedGroup: GroupTableItem | null = null, barTarget: number = DEFAULT_BAR_TARGET, timeRange?: { min: Moment; max: Moment } @@ -78,27 +78,27 @@ export const useData = ( return fieldStatsRequest ? { ...fieldStatsRequest, - selectedSignificantTerm, + selectedSignificantItem, selectedGroup, - includeSelectedSignificantTerm: false, + includeSelectedSignificantItem: false, } : undefined; - }, [fieldStatsRequest, selectedSignificantTerm, selectedGroup]); + }, [fieldStatsRequest, selectedSignificantItem, selectedGroup]); - const selectedSignificantTermStatsRequest = useMemo(() => { - return fieldStatsRequest && (selectedSignificantTerm || selectedGroup) + const selectedSignificantItemStatsRequest = useMemo(() => { + return fieldStatsRequest && (selectedSignificantItem || selectedGroup) ? { ...fieldStatsRequest, - selectedSignificantTerm, + selectedSignificantItem, selectedGroup, - includeSelectedSignificantTerm: true, + includeSelectedSignificantItem: true, } : undefined; - }, [fieldStatsRequest, selectedSignificantTerm, selectedGroup]); + }, [fieldStatsRequest, selectedSignificantItem, selectedGroup]); const documentStats = useDocumentCountStats( overallStatsRequest, - selectedSignificantTermStatsRequest, + selectedSignificantItemStatsRequest, lastRefresh ); 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 8cfaa074286d63..c154640acd4e84 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 @@ -94,7 +94,7 @@ export function useDocumentCountStats = [ +export const duplicateIdentifier: Array = [ 'doc_count', 'bg_count', 'total_doc_count', diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/fetch_categories.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/fetch_categories.ts index bb49622ad999c0..e70255ababffde 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/fetch_categories.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/fetch_categories.ts @@ -16,7 +16,7 @@ import { } from '@kbn/ml-random-sampler-utils'; import { RANDOM_SAMPLER_SEED } from '../../../../common/constants'; -import type { AiopsLogRateAnalysisSchema } from '../../../../common/api/log_rate_analysis'; +import type { AiopsLogRateAnalysisSchema } from '../../../../common/api/log_rate_analysis/schema'; import { createCategoryRequest } from '../../../../common/api/log_categorization/create_category_request'; import type { Category, diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/fetch_category_counts.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/fetch_category_counts.ts index fc7c14fb8f2eec..608734ae0e19a7 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/fetch_category_counts.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/fetch_category_counts.ts @@ -12,7 +12,7 @@ import { ElasticsearchClient } from '@kbn/core/server'; import type { Logger } from '@kbn/logging'; import { isPopulatedObject } from '@kbn/ml-is-populated-object'; -import type { AiopsLogRateAnalysisSchema } from '../../../../common/api/log_rate_analysis'; +import type { AiopsLogRateAnalysisSchema } from '../../../../common/api/log_rate_analysis/schema'; import { getCategoryQuery } from '../../../../common/api/log_categorization/get_category_query'; import type { Category } from '../../../../common/api/log_categorization/types'; diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/fetch_frequent_item_sets.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/fetch_frequent_item_sets.ts index c74030efaea7fe..ff02251b64f954 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/fetch_frequent_item_sets.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/fetch_frequent_item_sets.ts @@ -12,12 +12,12 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import type { Logger } from '@kbn/logging'; -import { type SignificantTerm } from '@kbn/ml-agg-utils'; +import { type SignificantItem } from '@kbn/ml-agg-utils'; import { createRandomSamplerWrapper } from '@kbn/ml-random-sampler-utils'; import { RANDOM_SAMPLER_SEED, LOG_RATE_ANALYSIS_SETTINGS } from '../../../../common/constants'; import type { - SignificantTermDuplicateGroup, + SignificantItemDuplicateGroup, ItemSet, FetchFrequentItemSetsResponse, } from '../../../../common/types'; @@ -29,10 +29,10 @@ interface FrequentItemSetsAggregation extends estypes.AggregationsSamplerAggrega } export function groupDuplicates( - cps: SignificantTerm[], - uniqueFields: Array + cps: SignificantItem[], + uniqueFields: Array ) { - const groups: SignificantTermDuplicateGroup[] = []; + const groups: SignificantItemDuplicateGroup[] = []; for (const cp of cps) { const compareAttributes = pick(cp, uniqueFields); @@ -51,16 +51,16 @@ export function groupDuplicates( return groups; } -export function getShouldClauses(significantTerms: SignificantTerm[]) { +export function getShouldClauses(significantItems: SignificantItem[]) { return Array.from( - group(significantTerms, ({ fieldName }) => fieldName), + group(significantItems, ({ fieldName }) => fieldName), ([field, values]) => ({ terms: { [field]: values.map((d) => d.fieldValue) } }) ); } -export function getFrequentItemSetsAggFields(significantTerms: SignificantTerm[]) { +export function getFrequentItemSetsAggFields(significantItems: SignificantItem[]) { return Array.from( - group(significantTerms, ({ fieldName }) => fieldName), + group(significantItems, ({ fieldName }) => fieldName), ([field, values]) => ({ field, include: values.map((d) => String(d.fieldValue)) }) ); } @@ -69,7 +69,7 @@ export async function fetchFrequentItemSets( client: ElasticsearchClient, index: string, searchQuery: estypes.QueryDslQueryContainer, - significantTerms: SignificantTerm[], + significantItems: SignificantItem[], timeFieldName: string, deviationMin: number, deviationMax: number, @@ -80,7 +80,7 @@ export async function fetchFrequentItemSets( abortSignal?: AbortSignal ): Promise { // Sort significant terms by ascending p-value, necessary to apply the field limit correctly. - const sortedSignificantTerms = significantTerms.slice().sort((a, b) => { + const sortedSignificantItems = significantItems.slice().sort((a, b) => { return (a.pValue ?? 0) - (b.pValue ?? 0); }); @@ -98,7 +98,7 @@ export async function fetchFrequentItemSets( }, }, ], - should: getShouldClauses(sortedSignificantTerms), + should: getShouldClauses(sortedSignificantItems), }, }; @@ -108,7 +108,7 @@ export async function fetchFrequentItemSets( minimum_set_size: 2, size: 200, minimum_support: LOG_RATE_ANALYSIS_SETTINGS.FREQUENT_ITEMS_SETS_MINIMUM_SUPPORT, - fields: getFrequentItemSetsAggFields(sortedSignificantTerms), + fields: getFrequentItemSetsAggFields(sortedSignificantItems), }, }, }; @@ -177,7 +177,7 @@ export async function fetchFrequentItemSets( Object.entries(fis.key).forEach(([key, value]) => { result.set[key] = value[0]; - const pValue = sortedSignificantTerms.find( + const pValue = sortedSignificantItems.find( (t) => t.fieldName === key && t.fieldValue === value[0] )?.pValue; diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/fetch_index_info.test.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/fetch_index_info.test.ts index 790989df9833ce..db14c30caebdcc 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/fetch_index_info.test.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/fetch_index_info.test.ts @@ -9,7 +9,7 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { ElasticsearchClient } from '@kbn/core/server'; -import type { AiopsLogRateAnalysisSchema } from '../../../../common/api/log_rate_analysis'; +import type { AiopsLogRateAnalysisSchema } from '../../../../common/api/log_rate_analysis/schema'; import { fetchIndexInfo, getRandomDocsRequest } from './fetch_index_info'; diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/fetch_index_info.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/fetch_index_info.ts index cfb9426a739adb..595e2121594372 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/fetch_index_info.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/fetch_index_info.ts @@ -10,7 +10,7 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { ES_FIELD_TYPES } from '@kbn/field-types'; import type { ElasticsearchClient } from '@kbn/core/server'; -import type { AiopsLogRateAnalysisSchema } from '../../../../common/api/log_rate_analysis'; +import type { AiopsLogRateAnalysisSchema } from '../../../../common/api/log_rate_analysis/schema'; import { getQueryWithParams } from './get_query_with_params'; import { getRequestBase } from './get_request_base'; diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/fetch_significant_categories.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/fetch_significant_categories.ts index c9e54be509426f..49617a1661ba65 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/fetch_significant_categories.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/fetch_significant_categories.ts @@ -10,10 +10,10 @@ import { uniq } from 'lodash'; import { ElasticsearchClient } from '@kbn/core/server'; import type { Logger } from '@kbn/logging'; import { criticalTableLookup, type Histogram } from '@kbn/ml-chi2test'; -import { type SignificantTerm, SIGNIFICANT_TERM_TYPE } from '@kbn/ml-agg-utils'; +import { type SignificantItem, SIGNIFICANT_ITEM_TYPE } from '@kbn/ml-agg-utils'; import type { Category } from '../../../../common/api/log_categorization/types'; -import type { AiopsLogRateAnalysisSchema } from '../../../../common/api/log_rate_analysis'; +import type { AiopsLogRateAnalysisSchema } from '../../../../common/api/log_rate_analysis/schema'; import { LOG_RATE_ANALYSIS_SETTINGS } from '../../../../common/constants'; import { fetchCategories } from './fetch_categories'; @@ -83,7 +83,7 @@ export const fetchSignificantCategories = async ( if (categoriesOverall.length !== fieldNames.length) return []; - const significantCategories: SignificantTerm[] = []; + const significantCategories: SignificantItem[] = []; // Using for...of to allow `await` within the loop. for (const [i, fieldName] of fieldNames.entries()) { @@ -152,7 +152,7 @@ export const fetchSignificantCategories = async ( score, pValue, normalizedScore: getNormalizedScore(score), - type: SIGNIFICANT_TERM_TYPE.LOG_PATTERN, + type: SIGNIFICANT_ITEM_TYPE.LOG_PATTERN, }); } }); diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/fetch_significant_term_p_values.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/fetch_significant_term_p_values.ts index 00bcd918f48d97..6cdf6983d58273 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/fetch_significant_term_p_values.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/fetch_significant_term_p_values.ts @@ -9,14 +9,14 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { ElasticsearchClient } from '@kbn/core/server'; import type { Logger } from '@kbn/logging'; -import { type SignificantTerm, SIGNIFICANT_TERM_TYPE } from '@kbn/ml-agg-utils'; +import { type SignificantItem, SIGNIFICANT_ITEM_TYPE } from '@kbn/ml-agg-utils'; import { createRandomSamplerWrapper, type RandomSamplerWrapper, } from '@kbn/ml-random-sampler-utils'; import { LOG_RATE_ANALYSIS_SETTINGS, RANDOM_SAMPLER_SEED } from '../../../../common/constants'; -import type { AiopsLogRateAnalysisSchema } from '../../../../common/api/log_rate_analysis'; +import type { AiopsLogRateAnalysisSchema } from '../../../../common/api/log_rate_analysis/schema'; import { isRequestAbortedError } from '../../../lib/is_request_aborted_error'; @@ -111,13 +111,13 @@ export const fetchSignificantTermPValues = async ( sampleProbability: number = 1, emitError: (m: string) => void, abortSignal?: AbortSignal -): Promise => { +): Promise => { const randomSamplerWrapper = createRandomSamplerWrapper({ probability: sampleProbability, seed: RANDOM_SAMPLER_SEED, }); - const result: SignificantTerm[] = []; + const result: SignificantItem[] = []; const settledPromises = await Promise.allSettled( fieldNames.map((fieldName) => @@ -168,7 +168,7 @@ export const fetchSignificantTermPValues = async ( if (typeof pValue === 'number' && pValue < LOG_RATE_ANALYSIS_SETTINGS.P_VALUE_THRESHOLD) { result.push({ key: `${fieldName}:${String(bucket.key)}`, - type: SIGNIFICANT_TERM_TYPE.KEYWORD, + type: SIGNIFICANT_ITEM_TYPE.KEYWORD, fieldName, fieldValue: String(bucket.key), doc_count: bucket.doc_count, diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/fetch_terms_2_categories_counts.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/fetch_terms_2_categories_counts.ts index f1629f212ae994..b9eac01041be1e 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/fetch_terms_2_categories_counts.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/fetch_terms_2_categories_counts.ts @@ -11,10 +11,10 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import type { Logger } from '@kbn/logging'; -import type { FieldValuePair, SignificantTerm } from '@kbn/ml-agg-utils'; +import type { FieldValuePair, SignificantItem } from '@kbn/ml-agg-utils'; import { isPopulatedObject } from '@kbn/ml-is-populated-object'; -import type { AiopsLogRateAnalysisSchema } from '../../../../common/api/log_rate_analysis'; +import type { AiopsLogRateAnalysisSchema } from '../../../../common/api/log_rate_analysis/schema'; import type { FetchFrequentItemSetsResponse, ItemSet } from '../../../../common/types'; import { getCategoryQuery } from '../../../../common/api/log_categorization/get_category_query'; import type { Category } from '../../../../common/api/log_categorization/types'; @@ -68,9 +68,9 @@ export async function fetchTerms2CategoriesCounts( esClient: ElasticsearchClient, params: AiopsLogRateAnalysisSchema, searchQuery: estypes.QueryDslQueryContainer, - significantTerms: SignificantTerm[], + significantTerms: SignificantItem[], itemSets: ItemSet[], - significantCategories: SignificantTerm[], + significantCategories: SignificantItem[], from: number, to: number, logger: Logger, diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_field_value_pair_counts.test.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_field_value_pair_counts.test.ts index fb04844c5bd3dc..903755eca45d5e 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_field_value_pair_counts.test.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_field_value_pair_counts.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { significantTermGroups } from '../../../../common/__mocks__/farequote/significant_term_groups'; +import { significantItemGroups } from '../../../../common/__mocks__/farequote/significant_item_groups'; import { fields } from '../../../../common/__mocks__/artificial_logs/fields'; import { filteredFrequentItemSets } from '../../../../common/__mocks__/artificial_logs/filtered_frequent_item_sets'; import { significantTerms } from '../../../../common/__mocks__/artificial_logs/significant_terms'; @@ -16,7 +16,7 @@ import { getSimpleHierarchicalTreeLeaves } from './get_simple_hierarchical_tree_ describe('getFieldValuePairCounts', () => { it('returns a nested record with field/value pair counts for farequote', () => { - const fieldValuePairCounts = getFieldValuePairCounts(significantTermGroups); + const fieldValuePairCounts = getFieldValuePairCounts(significantItemGroups); expect(fieldValuePairCounts).toEqual({ airline: { diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_field_value_pair_counts.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_field_value_pair_counts.ts index 429306139d4027..6d0e15f2117f8d 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_field_value_pair_counts.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_field_value_pair_counts.ts @@ -5,14 +5,14 @@ * 2.0. */ -import type { SignificantTermGroup } from '@kbn/ml-agg-utils'; +import type { SignificantItemGroup } from '@kbn/ml-agg-utils'; import type { FieldValuePairCounts } from '../../../../common/types'; /** * Get a nested record of field/value pairs with counts */ -export function getFieldValuePairCounts(cpgs: SignificantTermGroup[]): FieldValuePairCounts { +export function getFieldValuePairCounts(cpgs: SignificantItemGroup[]): FieldValuePairCounts { return cpgs.reduce((p, { group }) => { group.forEach(({ fieldName, fieldValue }) => { if (p[fieldName] === undefined) { diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_filters.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_filters.ts index 5e58e138dac6b1..e910d2e997441e 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_filters.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_filters.ts @@ -9,7 +9,7 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { ESFilter } from '@kbn/es-types'; -import type { AiopsLogRateAnalysisSchema } from '../../../../common/api/log_rate_analysis'; +import type { AiopsLogRateAnalysisSchema } from '../../../../common/api/log_rate_analysis/schema'; export function rangeQuery( start?: number, diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_group_filter.test.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_group_filter.test.ts index e4d30a4438c6e0..9f583f9e199df7 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_group_filter.test.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_group_filter.test.ts @@ -5,13 +5,13 @@ * 2.0. */ -import { finalSignificantTermGroups } from '../../../../common/__mocks__/artificial_logs/final_significant_term_groups'; +import { finalSignificantItemGroups } from '../../../../common/__mocks__/artificial_logs/final_significant_item_groups'; import { getGroupFilter } from './get_group_filter'; describe('getGroupFilter', () => { - it('gets a query filter for the significant terms of a group', () => { - expect(getGroupFilter(finalSignificantTermGroups[0])).toStrictEqual([ + it('gets a query filter for the significant items of a group', () => { + expect(getGroupFilter(finalSignificantItemGroups[0])).toStrictEqual([ { term: { url: 'login.php', diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_group_filter.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_group_filter.ts index d968ce90ec91b8..e2ae795004e180 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_group_filter.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_group_filter.ts @@ -7,21 +7,21 @@ import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { type SignificantTermGroup, SIGNIFICANT_TERM_TYPE } from '@kbn/ml-agg-utils'; +import { type SignificantItemGroup, SIGNIFICANT_ITEM_TYPE } from '@kbn/ml-agg-utils'; import { getCategoryQuery } from '../../../../common/api/log_categorization/get_category_query'; -// Transforms a list of significant terms from a group in a query filter. +// Transforms a list of significant items from a group in a query filter. // Uses a `term` filter for single field value combinations. // For fields with multiple values it creates a single `terms` filter that includes // all values. This avoids queries not returning any results otherwise because // separate `term` filter for multiple values for the same field would rule each other out. export function getGroupFilter( - significantTermGroup: SignificantTermGroup + significantItemGroup: SignificantItemGroup ): estypes.QueryDslQueryContainer[] { const groupKeywordFilter = Object.entries( - significantTermGroup.group - .filter((d) => d.type === SIGNIFICANT_TERM_TYPE.KEYWORD) + significantItemGroup.group + .filter((d) => d.type === SIGNIFICANT_ITEM_TYPE.KEYWORD) .reduce>>((p, c) => { if (p[c.fieldName]) { p[c.fieldName].push(c.fieldValue); @@ -35,8 +35,8 @@ export function getGroupFilter( return p; }, []); - const groupLogPatternFilter = significantTermGroup.group - .filter((d) => d.type === SIGNIFICANT_TERM_TYPE.LOG_PATTERN) + const groupLogPatternFilter = significantItemGroup.group + .filter((d) => d.type === SIGNIFICANT_ITEM_TYPE.LOG_PATTERN) .map((d) => getCategoryQuery(d.fieldName, [ { diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_groups_with_readded_duplicates.test.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_groups_with_readded_duplicates.test.ts index 3c7325cdb49ebd..5a0e1507f398d8 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_groups_with_readded_duplicates.test.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_groups_with_readded_duplicates.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { significantTermGroups } from '../../../../common/__mocks__/artificial_logs/significant_term_groups'; +import { significantItemGroups } from '../../../../common/__mocks__/artificial_logs/significant_item_groups'; import { significantTerms } from '../../../../common/__mocks__/artificial_logs/significant_terms'; import { duplicateIdentifier } from './duplicate_identifier'; @@ -16,15 +16,15 @@ import { getMarkedDuplicates } from './get_marked_duplicates'; describe('getGroupsWithReaddedDuplicates', () => { it('gets groups with readded duplicates', () => { - const groupedSignificantTerms = groupDuplicates(significantTerms, duplicateIdentifier).filter( + const groupedSignificantItems = groupDuplicates(significantTerms, duplicateIdentifier).filter( (g) => g.group.length > 1 ); - const fieldValuePairCounts = getFieldValuePairCounts(significantTermGroups); - const markedDuplicates = getMarkedDuplicates(significantTermGroups, fieldValuePairCounts); + const fieldValuePairCounts = getFieldValuePairCounts(significantItemGroups); + const markedDuplicates = getMarkedDuplicates(significantItemGroups, fieldValuePairCounts); const groupsWithReaddedDuplicates = getGroupsWithReaddedDuplicates( markedDuplicates, - groupedSignificantTerms + groupedSignificantItems ); expect(groupsWithReaddedDuplicates).toEqual([ diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_groups_with_readded_duplicates.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_groups_with_readded_duplicates.ts index 6defb9d8866625..e3e012ce299e3e 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_groups_with_readded_duplicates.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_groups_with_readded_duplicates.ts @@ -7,20 +7,20 @@ import { uniqWith, isEqual } from 'lodash'; -import type { SignificantTermGroup } from '@kbn/ml-agg-utils'; +import type { SignificantItemGroup } from '@kbn/ml-agg-utils'; -import type { SignificantTermDuplicateGroup } from '../../../../common/types'; +import type { SignificantItemDuplicateGroup } from '../../../../common/types'; export function getGroupsWithReaddedDuplicates( - groups: SignificantTermGroup[], - groupedSignificantTerms: SignificantTermDuplicateGroup[] -): SignificantTermGroup[] { + groups: SignificantItemGroup[], + groupedSignificantItems: SignificantItemDuplicateGroup[] +): SignificantItemGroup[] { return groups.map((g) => { const group = [...g.group]; for (const groupItem of g.group) { const { duplicate } = groupItem; - const duplicates = groupedSignificantTerms.find((d) => + const duplicates = groupedSignificantItems.find((d) => d.group.some( (dg) => dg.fieldName === groupItem.fieldName && dg.fieldValue === groupItem.fieldValue ) diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_histogram_query.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_histogram_query.ts index b45d7d026638e9..0f3510df73dcf2 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_histogram_query.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_histogram_query.ts @@ -7,7 +7,7 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import type { AiopsLogRateAnalysisSchema } from '../../../../common/api/log_rate_analysis'; +import type { AiopsLogRateAnalysisSchema } from '../../../../common/api/log_rate_analysis/schema'; import { getQueryWithParams } from './get_query_with_params'; diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_marked_duplicates.test.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_marked_duplicates.test.ts index 9c0d86a392e4d8..fcc2448d4cd464 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_marked_duplicates.test.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_marked_duplicates.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { significantTermGroups } from '../../../../common/__mocks__/farequote/significant_term_groups'; +import { significantItemGroups } from '../../../../common/__mocks__/farequote/significant_item_groups'; import { fields } from '../../../../common/__mocks__/artificial_logs/fields'; import { filteredFrequentItemSets } from '../../../../common/__mocks__/artificial_logs/filtered_frequent_item_sets'; import { significantTerms } from '../../../../common/__mocks__/artificial_logs/significant_terms'; @@ -16,9 +16,9 @@ import { getSimpleHierarchicalTree } from './get_simple_hierarchical_tree'; import { getSimpleHierarchicalTreeLeaves } from './get_simple_hierarchical_tree_leaves'; describe('markDuplicates', () => { - it('marks duplicates based on significant terms groups for farequote', () => { - const fieldValuePairCounts = getFieldValuePairCounts(significantTermGroups); - const markedDuplicates = getMarkedDuplicates(significantTermGroups, fieldValuePairCounts); + it('marks duplicates based on significant items groups for farequote', () => { + const fieldValuePairCounts = getFieldValuePairCounts(significantItemGroups); + const markedDuplicates = getMarkedDuplicates(significantItemGroups, fieldValuePairCounts); expect(markedDuplicates).toEqual([ { @@ -74,7 +74,7 @@ describe('markDuplicates', () => { ]); }); - it('marks duplicates based on significant terms groups for artificial logs', () => { + it('marks duplicates based on significant items groups for artificial logs', () => { const simpleHierarchicalTree = getSimpleHierarchicalTree( filteredFrequentItemSets, true, diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_marked_duplicates.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_marked_duplicates.ts index 7708d0634bda37..8ddc13cfee8df8 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_marked_duplicates.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_marked_duplicates.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { SignificantTermGroup } from '@kbn/ml-agg-utils'; +import type { SignificantItemGroup } from '@kbn/ml-agg-utils'; import type { FieldValuePairCounts } from '../../../../common/types'; @@ -13,9 +13,9 @@ import type { FieldValuePairCounts } from '../../../../common/types'; * Analyse duplicate field/value pairs in groups. */ export function getMarkedDuplicates( - cpgs: SignificantTermGroup[], + cpgs: SignificantItemGroup[], fieldValuePairCounts: FieldValuePairCounts -): SignificantTermGroup[] { +): SignificantItemGroup[] { return cpgs.map((cpg) => { return { ...cpg, diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_missing_significant_terms.test.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_missing_significant_items.test.ts similarity index 75% rename from x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_missing_significant_terms.test.ts rename to x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_missing_significant_items.test.ts index 412b7013b64d61..8ac70af16d298b 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_missing_significant_terms.test.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_missing_significant_items.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { significantTermGroups } from '../../../../common/__mocks__/artificial_logs/significant_term_groups'; +import { significantItemGroups } from '../../../../common/__mocks__/artificial_logs/significant_item_groups'; import { significantTerms } from '../../../../common/__mocks__/artificial_logs/significant_terms'; import { duplicateIdentifier } from './duplicate_identifier'; @@ -13,27 +13,27 @@ import { getGroupsWithReaddedDuplicates } from './get_groups_with_readded_duplic import { groupDuplicates } from './fetch_frequent_item_sets'; import { getFieldValuePairCounts } from './get_field_value_pair_counts'; import { getMarkedDuplicates } from './get_marked_duplicates'; -import { getMissingSignificantTerms } from './get_missing_significant_terms'; +import { getMissingSignificantItems } from './get_missing_significant_items'; -describe('getMissingSignificantTerms', () => { - it('get missing significant terms', () => { - const groupedSignificantTerms = groupDuplicates(significantTerms, duplicateIdentifier).filter( +describe('getMissingSignificantItems', () => { + it('get missing significant items', () => { + const groupedSignificantItems = groupDuplicates(significantTerms, duplicateIdentifier).filter( (g) => g.group.length > 1 ); - const fieldValuePairCounts = getFieldValuePairCounts(significantTermGroups); - const markedDuplicates = getMarkedDuplicates(significantTermGroups, fieldValuePairCounts); + const fieldValuePairCounts = getFieldValuePairCounts(significantItemGroups); + const markedDuplicates = getMarkedDuplicates(significantItemGroups, fieldValuePairCounts); const groupsWithReaddedDuplicates = getGroupsWithReaddedDuplicates( markedDuplicates, - groupedSignificantTerms + groupedSignificantItems ); - const missingSignificantTerms = getMissingSignificantTerms( + const missingSignificantItems = getMissingSignificantItems( significantTerms, groupsWithReaddedDuplicates ); - expect(missingSignificantTerms).toEqual([ + expect(missingSignificantItems).toEqual([ { key: 'user:Peter', type: 'keyword', diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_missing_significant_terms.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_missing_significant_items.ts similarity index 57% rename from x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_missing_significant_terms.ts rename to x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_missing_significant_items.ts index 7daa797f16a370..c2847f1592dd10 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_missing_significant_terms.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_missing_significant_items.ts @@ -5,14 +5,14 @@ * 2.0. */ -import type { SignificantTerm, SignificantTermGroup } from '@kbn/ml-agg-utils'; +import type { SignificantItem, SignificantItemGroup } from '@kbn/ml-agg-utils'; -export function getMissingSignificantTerms( - significantTerms: SignificantTerm[], - significantTermGroups: SignificantTermGroup[] +export function getMissingSignificantItems( + significantItems: SignificantItem[], + significantItemGroups: SignificantItemGroup[] ) { - return significantTerms.filter((cp) => { - return !significantTermGroups.some((cpg) => { + return significantItems.filter((cp) => { + return !significantItemGroups.some((cpg) => { return cpg.group.some((d) => d.fieldName === cp.fieldName && d.fieldValue === cp.fieldValue); }); }); diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_query_with_params.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_query_with_params.ts index 2d68c666b78ea9..b7e2e2619316a9 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_query_with_params.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_query_with_params.ts @@ -9,7 +9,7 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { FieldValuePair } from '@kbn/ml-agg-utils'; -import type { AiopsLogRateAnalysisSchema } from '../../../../common/api/log_rate_analysis'; +import type { AiopsLogRateAnalysisSchema } from '../../../../common/api/log_rate_analysis/schema'; import { getFilters } from './get_filters'; @@ -18,7 +18,7 @@ export const getTermsQuery = ({ fieldName, fieldValue }: FieldValuePair) => { }; interface QueryParams { - params: AiopsLogRateAnalysisSchema; + params: AiopsLogRateAnalysisSchema<'2'>; termFilters?: FieldValuePair[]; filter?: estypes.QueryDslQueryContainer; } diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_request_base.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_request_base.ts index 2410be74ea6b01..ddc1a2b3a0a343 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_request_base.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_request_base.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { AiopsLogRateAnalysisSchema } from '../../../../common/api/log_rate_analysis'; +import type { AiopsLogRateAnalysisSchema } from '../../../../common/api/log_rate_analysis/schema'; export const getRequestBase = ({ index, includeFrozen }: AiopsLogRateAnalysisSchema) => ({ index, diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_significant_term_groups.test.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_significant_item_groups.test.ts similarity index 59% rename from x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_significant_term_groups.test.ts rename to x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_significant_item_groups.test.ts index 05a682bc4ea344..cf5ba41fe5216a 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_significant_term_groups.test.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_significant_item_groups.test.ts @@ -10,20 +10,20 @@ import { orderBy } from 'lodash'; import { fields } from '../../../../common/__mocks__/artificial_logs/fields'; import { frequentItemSets } from '../../../../common/__mocks__/artificial_logs/frequent_item_sets'; import { significantTerms } from '../../../../common/__mocks__/artificial_logs/significant_terms'; -import { finalSignificantTermGroups } from '../../../../common/__mocks__/artificial_logs/final_significant_term_groups'; +import { finalSignificantItemGroups } from '../../../../common/__mocks__/artificial_logs/final_significant_item_groups'; -import { getSignificantTermGroups } from './get_significant_term_groups'; +import { getSignificantItemGroups } from './get_significant_item_groups'; -describe('getSignificantTermGroups', () => { - it('gets significant terms groups', () => { - const significantTermGroups = getSignificantTermGroups( +describe('getSignificantItemGroups', () => { + it('gets significant items groups', () => { + const significantItemGroups = getSignificantItemGroups( frequentItemSets, significantTerms, fields ); - expect(orderBy(significantTermGroups, ['docCount'])).toEqual( - orderBy(finalSignificantTermGroups, ['docCount']) + expect(orderBy(significantItemGroups, ['docCount'])).toEqual( + orderBy(finalSignificantItemGroups, ['docCount']) ); }); }); diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_significant_term_groups.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_significant_item_groups.ts similarity index 70% rename from x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_significant_term_groups.ts rename to x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_significant_item_groups.ts index 1b498b0d765954..27dc06ad343b93 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_significant_term_groups.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_significant_item_groups.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { SignificantTerm, SignificantTermGroup } from '@kbn/ml-agg-utils'; +import type { SignificantItem, SignificantItemGroup } from '@kbn/ml-agg-utils'; import { duplicateIdentifier } from './duplicate_identifier'; import { groupDuplicates } from './fetch_frequent_item_sets'; @@ -13,18 +13,18 @@ import { getFieldValuePairCounts } from './get_field_value_pair_counts'; import { getMarkedDuplicates } from './get_marked_duplicates'; import { getSimpleHierarchicalTree } from './get_simple_hierarchical_tree'; import { getSimpleHierarchicalTreeLeaves } from './get_simple_hierarchical_tree_leaves'; -import { getMissingSignificantTerms } from './get_missing_significant_terms'; -import { transformSignificantTermToGroup } from './transform_significant_term_to_group'; +import { getMissingSignificantItems } from './get_missing_significant_items'; +import { transformSignificantItemToGroup } from './transform_significant_item_to_group'; import type { ItemSet } from '../../../../common/types'; -export function getSignificantTermGroups( +export function getSignificantItemGroups( itemsets: ItemSet[], - significantTerms: SignificantTerm[], + significantItems: SignificantItem[], fields: string[] -): SignificantTermGroup[] { - // We use the grouped significant terms to later repopulate +): SignificantItemGroup[] { + // We use the grouped significant items to later repopulate // the `frequent_item_sets` result with the missing duplicates. - const groupedSignificantTerms = groupDuplicates(significantTerms, duplicateIdentifier).filter( + const groupedSignificantItems = groupDuplicates(significantItems, duplicateIdentifier).filter( (g) => g.group.length > 1 ); @@ -33,7 +33,7 @@ export function getSignificantTermGroups( // and then summarize them in larger groups where possible. // Get a tree structure based on `frequent_item_sets`. - const { root } = getSimpleHierarchicalTree(itemsets, false, false, significantTerms, fields); + const { root } = getSimpleHierarchicalTree(itemsets, false, false, significantItems, fields); // Each leave of the tree will be a summarized group of co-occuring field/value pairs. const treeLeaves = getSimpleHierarchicalTreeLeaves(root, []); @@ -42,21 +42,21 @@ export function getSignificantTermGroups( // that occur in multiple groups. This will allow us to highlight field/value pairs that are // unique to a group in a better way. const fieldValuePairCounts = getFieldValuePairCounts(treeLeaves); - const significantTermGroups = getMarkedDuplicates(treeLeaves, fieldValuePairCounts); + const significantItemGroups = getMarkedDuplicates(treeLeaves, fieldValuePairCounts); // Some field/value pairs might not be part of the `frequent_item_sets` result set, for example // because they don't co-occur with other field/value pairs or because of the limits we set on the query. // In this next part we identify those missing pairs and add them as individual groups. - const missingSignificantTerms = getMissingSignificantTerms( - significantTerms, - significantTermGroups + const missingSignificantItems = getMissingSignificantItems( + significantItems, + significantItemGroups ); - significantTermGroups.push( - ...missingSignificantTerms.map((significantTerm) => - transformSignificantTermToGroup(significantTerm, groupedSignificantTerms) + significantItemGroups.push( + ...missingSignificantItems.map((significantItem) => + transformSignificantItemToGroup(significantItem, groupedSignificantItems) ) ); - return significantTermGroups; + return significantItemGroups; } diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_simple_hierarchical_tree.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_simple_hierarchical_tree.ts index 2ffcc184fa71e8..54cce87526bb12 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_simple_hierarchical_tree.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_simple_hierarchical_tree.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { SignificantTerm } from '@kbn/ml-agg-utils'; +import type { SignificantItem } from '@kbn/ml-agg-utils'; import type { ItemSet, SimpleHierarchicalTreeNode } from '../../../../common/types'; @@ -34,7 +34,7 @@ function NewNodeFactory(name: string): SimpleHierarchicalTreeNode { * The resulting tree components are non-overlapping subsets of the data. * In summary, we start with the most inclusive itemset (highest count), and perform a depth first search in field order. * - * @param significantTerms + * @param significantItems * @param fields * @param displayParent * @param parentDocCount @@ -47,7 +47,7 @@ function NewNodeFactory(name: string): SimpleHierarchicalTreeNode { * @returns */ function dfDepthFirstSearch( - significantTerms: SignificantTerm[], + significantItems: SignificantItem[], fields: string[], displayParent: SimpleHierarchicalTreeNode, parentDocCount: number, @@ -79,10 +79,10 @@ function dfDepthFirstSearch( let displayNode: SimpleHierarchicalTreeNode; - const significantTerm = significantTerms.find( + const significantItem = significantItems.find( (d) => d.fieldName === field && d.fieldValue === value ); - if (!significantTerm) { + if (!significantItem) { return 0; } @@ -91,8 +91,8 @@ function dfDepthFirstSearch( displayParent.name += ` ${value}`; displayParent.set.push({ - key: significantTerm.key, - type: significantTerm.type, + key: significantItem.key, + type: significantItem.type, fieldName: field, fieldValue: value, docCount, @@ -105,8 +105,8 @@ function dfDepthFirstSearch( displayNode = NewNodeFactory(`${docCount}/${totalDocCount}${label}`); displayNode.set = [...displayParent.set]; displayNode.set.push({ - key: significantTerm.key, - type: significantTerm.type, + key: significantItem.key, + type: significantItem.type, fieldName: field, fieldValue: value, docCount, @@ -148,7 +148,7 @@ function dfDepthFirstSearch( let subCount = 0; for (const nextValue of getValuesDescending(filteredItemSets, nextField)) { subCount += dfDepthFirstSearch( - significantTerms, + significantItems, fields, displayNode, docCount, @@ -181,7 +181,7 @@ export function getSimpleHierarchicalTree( itemSets: ItemSet[], collapseRedundant: boolean, displayOther: boolean, - significantTerms: SignificantTerm[], + significantItems: SignificantItem[], fields: string[] = [] ) { const totalDocCount = Math.max(...itemSets.map((d) => d.total_doc_count)); @@ -191,7 +191,7 @@ export function getSimpleHierarchicalTree( for (const field of fields) { for (const value of getValuesDescending(itemSets, field)) { dfDepthFirstSearch( - significantTerms, + significantItems, fields, newRoot, totalDocCount + 1, diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_simple_hierarchical_tree_leaves.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_simple_hierarchical_tree_leaves.ts index bd183239eeadf1..619ec2896d4d4b 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_simple_hierarchical_tree_leaves.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/get_simple_hierarchical_tree_leaves.ts @@ -6,7 +6,7 @@ */ import { orderBy } from 'lodash'; -import type { SignificantTermGroup } from '@kbn/ml-agg-utils'; +import type { SignificantItemGroup } from '@kbn/ml-agg-utils'; import { stringHash } from '@kbn/ml-string-hash'; import type { SimpleHierarchicalTreeNode } from '../../../../common/types'; @@ -16,7 +16,7 @@ import type { SimpleHierarchicalTreeNode } from '../../../../common/types'; */ export function getSimpleHierarchicalTreeLeaves( tree: SimpleHierarchicalTreeNode, - leaves: SignificantTermGroup[], + leaves: SignificantItemGroup[], level = 1 ) { if (tree.children.length === 0) { @@ -43,7 +43,7 @@ export function getSimpleHierarchicalTreeLeaves( const sortedLeaves = orderBy(leaves, [(d) => d.group.length], ['desc']); // Checks if a group is a subset of items already present in a larger group. - const filteredLeaves = sortedLeaves.reduce((p, c) => { + const filteredLeaves = sortedLeaves.reduce((p, c) => { const isSubset = p.some((pG) => c.group.every((cGI) => pG.group.some((pGI) => pGI.fieldName === cGI.fieldName && pGI.fieldValue === cGI.fieldValue) diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/transform_significant_term_to_group.test.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/transform_significant_item_to_group.test.ts similarity index 66% rename from x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/transform_significant_term_to_group.test.ts rename to x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/transform_significant_item_to_group.test.ts index 860baa3c800a20..f1a2a540dc8770 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/transform_significant_term_to_group.test.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/transform_significant_item_to_group.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { significantTermGroups } from '../../../../common/__mocks__/artificial_logs/significant_term_groups'; +import { significantItemGroups } from '../../../../common/__mocks__/artificial_logs/significant_item_groups'; import { significantTerms } from '../../../../common/__mocks__/artificial_logs/significant_terms'; import { duplicateIdentifier } from './duplicate_identifier'; @@ -13,30 +13,30 @@ import { getGroupsWithReaddedDuplicates } from './get_groups_with_readded_duplic import { groupDuplicates } from './fetch_frequent_item_sets'; import { getFieldValuePairCounts } from './get_field_value_pair_counts'; import { getMarkedDuplicates } from './get_marked_duplicates'; -import { getMissingSignificantTerms } from './get_missing_significant_terms'; -import { transformSignificantTermToGroup } from './transform_significant_term_to_group'; +import { getMissingSignificantItems } from './get_missing_significant_items'; +import { transformSignificantItemToGroup } from './transform_significant_item_to_group'; -describe('getMissingSignificantTerms', () => { - it('get missing significant terms', () => { - const groupedSignificantTerms = groupDuplicates(significantTerms, duplicateIdentifier).filter( +describe('getMissingSignificantItems', () => { + it('get missing significant items', () => { + const groupedSignificantItems = groupDuplicates(significantTerms, duplicateIdentifier).filter( (g) => g.group.length > 1 ); - const fieldValuePairCounts = getFieldValuePairCounts(significantTermGroups); - const markedDuplicates = getMarkedDuplicates(significantTermGroups, fieldValuePairCounts); + const fieldValuePairCounts = getFieldValuePairCounts(significantItemGroups); + const markedDuplicates = getMarkedDuplicates(significantItemGroups, fieldValuePairCounts); const groupsWithReaddedDuplicates = getGroupsWithReaddedDuplicates( markedDuplicates, - groupedSignificantTerms + groupedSignificantItems ); - const missingSignificantTerms = getMissingSignificantTerms( + const missingSignificantItems = getMissingSignificantItems( significantTerms, groupsWithReaddedDuplicates ); - const transformed = transformSignificantTermToGroup( - missingSignificantTerms[0], - groupedSignificantTerms + const transformed = transformSignificantItemToGroup( + missingSignificantItems[0], + groupedSignificantItems ); expect(transformed).toEqual({ diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/transform_significant_term_to_group.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/transform_significant_item_to_group.ts similarity index 77% rename from x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/transform_significant_term_to_group.ts rename to x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/transform_significant_item_to_group.ts index 9f95a5c0fa2dbd..05a1473acc2bd4 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/transform_significant_term_to_group.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/queries/transform_significant_item_to_group.ts @@ -6,17 +6,17 @@ */ import { stringHash } from '@kbn/ml-string-hash'; -import type { SignificantTerm, SignificantTermGroup } from '@kbn/ml-agg-utils'; +import type { SignificantItem, SignificantItemGroup } from '@kbn/ml-agg-utils'; -import type { SignificantTermDuplicateGroup } from '../../../../common/types'; +import type { SignificantItemDuplicateGroup } from '../../../../common/types'; -export function transformSignificantTermToGroup( - significantTerm: SignificantTerm, - groupedSignificantTerms: SignificantTermDuplicateGroup[] -): SignificantTermGroup { - const { key, type, fieldName, fieldValue, doc_count: docCount, pValue } = significantTerm; +export function transformSignificantItemToGroup( + significantItem: SignificantItem, + groupedSignificantItems: SignificantItemDuplicateGroup[] +): SignificantItemGroup { + const { key, type, fieldName, fieldValue, doc_count: docCount, pValue } = significantItem; - const duplicates = groupedSignificantTerms.find((d) => + const duplicates = groupedSignificantItems.find((d) => d.group.some((dg) => dg.fieldName === fieldName && dg.fieldValue === fieldValue) ); diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/route_handler_factory.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/route_handler_factory.ts index b522d6d6ee8187..30f3dbe55f446b 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/route_handler_factory.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/route_handler_factory.ts @@ -21,22 +21,23 @@ import { i18n } from '@kbn/i18n'; import { KBN_FIELD_TYPES } from '@kbn/field-types'; import { streamFactory } from '@kbn/ml-response-stream/server'; import type { - SignificantTerm, - SignificantTermGroup, + SignificantItem, + SignificantItemGroup, + SignificantItemHistogramItem, NumericChartData, NumericHistogramField, } from '@kbn/ml-agg-utils'; -import { SIGNIFICANT_TERM_TYPE } from '@kbn/ml-agg-utils'; +import { SIGNIFICANT_ITEM_TYPE } from '@kbn/ml-agg-utils'; import { fetchHistogramsForFields } from '@kbn/ml-agg-utils'; import { createExecutionContext } from '@kbn/ml-route-utils'; import type { UsageCounter } from '@kbn/usage-collection-plugin/server'; import { RANDOM_SAMPLER_SEED, AIOPS_TELEMETRY_ID } from '../../../common/constants'; import { - addSignificantTermsAction, - addSignificantTermsGroupAction, - addSignificantTermsGroupHistogramAction, - addSignificantTermsHistogramAction, + addSignificantItemsAction, + addSignificantItemsGroupAction, + addSignificantItemsGroupHistogramAction, + addSignificantItemsHistogramAction, addErrorAction, pingAction, resetAllAction, @@ -44,8 +45,11 @@ import { resetGroupsAction, updateLoadingStateAction, AiopsLogRateAnalysisApiAction, - type AiopsLogRateAnalysisSchema, -} from '../../../common/api/log_rate_analysis'; +} from '../../../common/api/log_rate_analysis/actions'; +import type { + AiopsLogRateAnalysisSchema, + AiopsLogRateAnalysisApiVersion as ApiVersion, +} from '../../../common/api/log_rate_analysis/schema'; import { getCategoryQuery } from '../../../common/api/log_categorization/get_category_query'; import { AIOPS_API_ENDPOINT } from '../../../common/api'; @@ -61,7 +65,7 @@ import { fetchFrequentItemSets } from './queries/fetch_frequent_item_sets'; import { fetchTerms2CategoriesCounts } from './queries/fetch_terms_2_categories_counts'; import { getHistogramQuery } from './queries/get_histogram_query'; import { getGroupFilter } from './queries/get_group_filter'; -import { getSignificantTermGroups } from './queries/get_significant_term_groups'; +import { getSignificantItemGroups } from './queries/get_significant_item_groups'; import { trackAIOpsRouteUsage } from '../../lib/track_route_usage'; // 10s ping frequency to keep the stream alive. @@ -74,16 +78,16 @@ const PROGRESS_STEP_GROUPING = 0.1; const PROGRESS_STEP_HISTOGRAMS = 0.1; const PROGRESS_STEP_HISTOGRAMS_GROUPS = 0.1; -export const routeHandlerFactory: ( +export function routeHandlerFactory( + version: T, license: AiopsLicense, logger: Logger, coreStart: CoreStart, usageCounter?: UsageCounter -) => RequestHandler = - (license, logger, coreStart, usageCounter) => - async ( +): RequestHandler> { + return async ( context: RequestHandlerContext, - request: KibanaRequest, + request: KibanaRequest>, response: KibanaResponseFactory ) => { const { headers } = request; @@ -135,7 +139,7 @@ export const routeHandlerFactory: ( end: streamEnd, push, responseWithHeaders, - } = streamFactory( + } = streamFactory>( request.headers, logger, request.body.compressResponse, @@ -283,16 +287,32 @@ export const routeHandlerFactory: ( } } - // Step 2: Significant Categories and Terms + // Step 2: Significant Categories and Items // This will store the combined count of detected significant log patterns and keywords let fieldValuePairsCount = 0; - const significantCategories: SignificantTerm[] = request.body.overrides?.significantTerms - ? request.body.overrides?.significantTerms.filter( - (d) => d.type === SIGNIFICANT_TERM_TYPE.LOG_PATTERN - ) - : []; + const significantCategories: SignificantItem[] = []; + + if (version === '1') { + significantCategories.push( + ...(( + request.body as AiopsLogRateAnalysisSchema<'1'> + ).overrides?.significantTerms?.filter( + (d) => d.type === SIGNIFICANT_ITEM_TYPE.LOG_PATTERN + ) ?? []) + ); + } + + if (version === '2') { + significantCategories.push( + ...(( + request.body as AiopsLogRateAnalysisSchema<'2'> + ).overrides?.significantItems?.filter( + (d) => d.type === SIGNIFICANT_ITEM_TYPE.LOG_PATTERN + ) ?? []) + ); + } // Get significant categories of text fields if (textFieldCandidates.length > 0) { @@ -309,15 +329,31 @@ export const routeHandlerFactory: ( ); if (significantCategories.length > 0) { - push(addSignificantTermsAction(significantCategories)); + push(addSignificantItemsAction(significantCategories, version)); } } - const significantTerms: SignificantTerm[] = request.body.overrides?.significantTerms - ? request.body.overrides?.significantTerms.filter( - (d) => d.type === SIGNIFICANT_TERM_TYPE.KEYWORD - ) - : []; + const significantTerms: SignificantItem[] = []; + + if (version === '1') { + significantTerms.push( + ...(( + request.body as AiopsLogRateAnalysisSchema<'1'> + ).overrides?.significantTerms?.filter( + (d) => d.type === SIGNIFICANT_ITEM_TYPE.KEYWORD + ) ?? []) + ); + } + + if (version === '2') { + significantTerms.push( + ...(( + request.body as AiopsLogRateAnalysisSchema<'2'> + ).overrides?.significantItems?.filter( + (d) => d.type === SIGNIFICANT_ITEM_TYPE.KEYWORD + ) ?? []) + ); + } const fieldsToSample = new Set(); @@ -375,7 +411,7 @@ export const routeHandlerFactory: ( }); significantTerms.push(...pValues); - push(addSignificantTermsAction(pValues)); + push(addSignificantItemsAction(pValues, version)); } push( @@ -535,7 +571,7 @@ export const routeHandlerFactory: ( } if (fields.length > 0 && itemSets.length > 0) { - const significantTermGroups = getSignificantTermGroups( + const significantItemGroups = getSignificantItemGroups( itemSets, [...significantTerms, ...significantCategories], fields @@ -543,10 +579,10 @@ export const routeHandlerFactory: ( // We'll find out if there's at least one group with at least two items, // only then will we return the groups to the clients and make the grouping option available. - const maxItems = Math.max(...significantTermGroups.map((g) => g.group.length)); + const maxItems = Math.max(...significantItemGroups.map((g) => g.group.length)); if (maxItems > 1) { - push(addSignificantTermsGroupAction(significantTermGroups)); + push(addSignificantItemsGroupAction(significantItemGroups, version)); } loaded += PROGRESS_STEP_GROUPING; @@ -559,9 +595,9 @@ export const routeHandlerFactory: ( return; } - logDebugMessage(`Fetch ${significantTermGroups.length} group histograms.`); + logDebugMessage(`Fetch ${significantItemGroups.length} group histograms.`); - const groupHistogramQueue = queue(async function (cpg: SignificantTermGroup) { + const groupHistogramQueue = queue(async function (cpg: SignificantItemGroup) { if (shouldStop) { logDebugMessage('shouldStop abort fetching group histograms.'); groupHistogramQueue.kill(); @@ -608,33 +644,46 @@ export const routeHandlerFactory: ( } return; } - const histogram = + const histogram: SignificantItemHistogramItem[] = overallTimeSeries.data.map((o) => { const current = cpgTimeSeries.data.find( (d1) => d1.key_as_string === o.key_as_string ) ?? { doc_count: 0, }; + + if (version === '1') { + return { + key: o.key, + key_as_string: o.key_as_string ?? '', + doc_count_significant_term: current.doc_count, + doc_count_overall: Math.max(0, o.doc_count - current.doc_count), + }; + } + return { key: o.key, key_as_string: o.key_as_string ?? '', - doc_count_significant_term: current.doc_count, + doc_count_significant_item: current.doc_count, doc_count_overall: Math.max(0, o.doc_count - current.doc_count), }; }) ?? []; push( - addSignificantTermsGroupHistogramAction([ - { - id: cpg.id, - histogram, - }, - ]) + addSignificantItemsGroupHistogramAction( + [ + { + id: cpg.id, + histogram, + }, + ], + version + ) ); } }, MAX_CONCURRENT_QUERIES); - groupHistogramQueue.push(significantTermGroups); + groupHistogramQueue.push(significantItemGroups); await groupHistogramQueue.drain(); } } catch (e) { @@ -657,7 +706,7 @@ export const routeHandlerFactory: ( overallTimeSeries !== undefined && !request.body.overrides?.regroupOnly ) { - const fieldValueHistogramQueue = queue(async function (cp: SignificantTerm) { + const fieldValueHistogramQueue = queue(async function (cp: SignificantItem) { if (shouldStop) { logDebugMessage('shouldStop abort fetching field/value histograms.'); fieldValueHistogramQueue.kill(); @@ -710,17 +759,26 @@ export const routeHandlerFactory: ( return; } - const histogram = + const histogram: SignificantItemHistogramItem[] = overallTimeSeries.data.map((o) => { const current = cpTimeSeries.data.find( (d1) => d1.key_as_string === o.key_as_string ) ?? { doc_count: 0, }; + if (version === '1') { + return { + key: o.key, + key_as_string: o.key_as_string ?? '', + doc_count_significant_term: current.doc_count, + doc_count_overall: Math.max(0, o.doc_count - current.doc_count), + }; + } + return { key: o.key, key_as_string: o.key_as_string ?? '', - doc_count_significant_term: current.doc_count, + doc_count_significant_item: current.doc_count, doc_count_overall: Math.max(0, o.doc_count - current.doc_count), }; }) ?? []; @@ -730,13 +788,16 @@ export const routeHandlerFactory: ( loaded += (1 / fieldValuePairsCount) * PROGRESS_STEP_HISTOGRAMS; pushHistogramDataLoadingState(); push( - addSignificantTermsHistogramAction([ - { - fieldName, - fieldValue, - histogram, - }, - ]) + addSignificantItemsHistogramAction( + [ + { + fieldName, + fieldValue, + histogram, + }, + ], + version + ) ); } }, MAX_CONCURRENT_QUERIES); @@ -802,17 +863,27 @@ export const routeHandlerFactory: ( return; } - const histogram = + const histogram: SignificantItemHistogramItem[] = overallTimeSeries.data.map((o) => { const current = catTimeSeries.data.find( (d1) => d1.key_as_string === o.key_as_string ) ?? { doc_count: 0, }; + + if (version === '1') { + return { + key: o.key, + key_as_string: o.key_as_string ?? '', + doc_count_significant_term: current.doc_count, + doc_count_overall: Math.max(0, o.doc_count - current.doc_count), + }; + } + return { key: o.key, key_as_string: o.key_as_string ?? '', - doc_count_significant_term: current.doc_count, + doc_count_significant_item: current.doc_count, doc_count_overall: Math.max(0, o.doc_count - current.doc_count), }; }) ?? []; @@ -822,13 +893,16 @@ export const routeHandlerFactory: ( loaded += (1 / fieldValuePairsCount) * PROGRESS_STEP_HISTOGRAMS; pushHistogramDataLoadingState(); push( - addSignificantTermsHistogramAction([ - { - fieldName, - fieldValue, - histogram, - }, - ]) + addSignificantItemsHistogramAction( + [ + { + fieldName, + fieldValue, + histogram, + }, + ], + version + ) ); } } @@ -849,3 +923,4 @@ export const routeHandlerFactory: ( return response.ok(responseWithHeaders); }); }; +} diff --git a/x-pack/plugins/alerting/public/hooks/use_archive_maintenance_window.test.tsx b/x-pack/plugins/alerting/public/hooks/use_archive_maintenance_window.test.tsx index e6bd2a4071b27f..d72f0bda9dc933 100644 --- a/x-pack/plugins/alerting/public/hooks/use_archive_maintenance_window.test.tsx +++ b/x-pack/plugins/alerting/public/hooks/use_archive_maintenance_window.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ import { act, renderHook } from '@testing-library/react-hooks/dom'; -import { waitFor } from '@testing-library/dom'; +import { waitFor } from '@testing-library/react'; import { MaintenanceWindow } from '../pages/maintenance_windows/types'; import { AppMockRenderer, createAppMockRenderer } from '../lib/test_utils'; diff --git a/x-pack/plugins/alerting/public/hooks/use_create_maintenance_window.test.tsx b/x-pack/plugins/alerting/public/hooks/use_create_maintenance_window.test.tsx index 195af1bb083e59..f827287532445c 100644 --- a/x-pack/plugins/alerting/public/hooks/use_create_maintenance_window.test.tsx +++ b/x-pack/plugins/alerting/public/hooks/use_create_maintenance_window.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ import { act, renderHook } from '@testing-library/react-hooks/dom'; -import { waitFor } from '@testing-library/dom'; +import { waitFor } from '@testing-library/react'; import { MaintenanceWindow } from '../pages/maintenance_windows/types'; import { AppMockRenderer, createAppMockRenderer } from '../lib/test_utils'; diff --git a/x-pack/plugins/alerting/public/hooks/use_find_maintenance_windows.test.tsx b/x-pack/plugins/alerting/public/hooks/use_find_maintenance_windows.test.tsx index 2080b9ff39d432..d21b145aea9372 100644 --- a/x-pack/plugins/alerting/public/hooks/use_find_maintenance_windows.test.tsx +++ b/x-pack/plugins/alerting/public/hooks/use_find_maintenance_windows.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ import { renderHook } from '@testing-library/react-hooks/dom'; -import { waitFor } from '@testing-library/dom'; +import { waitFor } from '@testing-library/react'; import { AppMockRenderer, createAppMockRenderer } from '../lib/test_utils'; import { useFindMaintenanceWindows } from './use_find_maintenance_windows'; diff --git a/x-pack/plugins/alerting/public/hooks/use_finish_and_archive_maintenance_window.test.tsx b/x-pack/plugins/alerting/public/hooks/use_finish_and_archive_maintenance_window.test.tsx index b80dbbae355bcd..453a3b88cef8fb 100644 --- a/x-pack/plugins/alerting/public/hooks/use_finish_and_archive_maintenance_window.test.tsx +++ b/x-pack/plugins/alerting/public/hooks/use_finish_and_archive_maintenance_window.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ import { act, renderHook } from '@testing-library/react-hooks/dom'; -import { waitFor } from '@testing-library/dom'; +import { waitFor } from '@testing-library/react'; import { MaintenanceWindow } from '../pages/maintenance_windows/types'; import { AppMockRenderer, createAppMockRenderer } from '../lib/test_utils'; diff --git a/x-pack/plugins/alerting/public/hooks/use_finish_maintenance_window.test.tsx b/x-pack/plugins/alerting/public/hooks/use_finish_maintenance_window.test.tsx index ed534cb835c8d5..06608125fd8367 100644 --- a/x-pack/plugins/alerting/public/hooks/use_finish_maintenance_window.test.tsx +++ b/x-pack/plugins/alerting/public/hooks/use_finish_maintenance_window.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ import { act, renderHook } from '@testing-library/react-hooks/dom'; -import { waitFor } from '@testing-library/dom'; +import { waitFor } from '@testing-library/react'; import { MaintenanceWindow } from '../pages/maintenance_windows/types'; import { AppMockRenderer, createAppMockRenderer } from '../lib/test_utils'; diff --git a/x-pack/plugins/alerting/public/hooks/use_get_maintenance_window.test.tsx b/x-pack/plugins/alerting/public/hooks/use_get_maintenance_window.test.tsx index eaef1f4fc4b99f..3003f1003ce12e 100644 --- a/x-pack/plugins/alerting/public/hooks/use_get_maintenance_window.test.tsx +++ b/x-pack/plugins/alerting/public/hooks/use_get_maintenance_window.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ import { renderHook } from '@testing-library/react-hooks/dom'; -import { waitFor } from '@testing-library/dom'; +import { waitFor } from '@testing-library/react'; import { AppMockRenderer, createAppMockRenderer } from '../lib/test_utils'; import { useGetMaintenanceWindow } from './use_get_maintenance_window'; diff --git a/x-pack/plugins/alerting/public/hooks/use_update_maintenance_window.test.tsx b/x-pack/plugins/alerting/public/hooks/use_update_maintenance_window.test.tsx index 897b44295d8c07..b29161f0e006da 100644 --- a/x-pack/plugins/alerting/public/hooks/use_update_maintenance_window.test.tsx +++ b/x-pack/plugins/alerting/public/hooks/use_update_maintenance_window.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ import { act, renderHook } from '@testing-library/react-hooks/dom'; -import { waitFor } from '@testing-library/dom'; +import { waitFor } from '@testing-library/react'; import { MaintenanceWindow } from '../pages/maintenance_windows/types'; import { AppMockRenderer, createAppMockRenderer } from '../lib/test_utils'; diff --git a/x-pack/plugins/alerting/public/pages/maintenance_windows/components/status_filter.test.tsx b/x-pack/plugins/alerting/public/pages/maintenance_windows/components/status_filter.test.tsx index f7cb8023323ef7..3875545e36df41 100644 --- a/x-pack/plugins/alerting/public/pages/maintenance_windows/components/status_filter.test.tsx +++ b/x-pack/plugins/alerting/public/pages/maintenance_windows/components/status_filter.test.tsx @@ -6,7 +6,7 @@ */ import { Query } from '@elastic/eui'; -import { fireEvent } from '@testing-library/dom'; +import { fireEvent } from '@testing-library/react'; import React from 'react'; import { AppMockRenderer, createAppMockRenderer } from '../../../lib/test_utils'; diff --git a/x-pack/plugins/alerting/public/pages/maintenance_windows/components/table_actions_popover.test.tsx b/x-pack/plugins/alerting/public/pages/maintenance_windows/components/table_actions_popover.test.tsx index 2b1a1057084f5a..8da3e12847e3a5 100644 --- a/x-pack/plugins/alerting/public/pages/maintenance_windows/components/table_actions_popover.test.tsx +++ b/x-pack/plugins/alerting/public/pages/maintenance_windows/components/table_actions_popover.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { fireEvent } from '@testing-library/dom'; +import { fireEvent } from '@testing-library/react'; import React from 'react'; import { AppMockRenderer, createAppMockRenderer } from '../../../lib/test_utils'; 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 3b3cd3a9728257..0c032dd6a3e92e 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 @@ -301,6 +301,7 @@ describe('Execution Handler', () => { foo: true, stateVal: 'My goes here', }, + ruleName: rule.name, }); expect(ruleRunMetricsStore.getTriggeredActionsStatus()).toBe(ActionsCompletion.COMPLETE); @@ -1988,6 +1989,7 @@ describe('Execution Handler', () => { "val": "rule url: http://localhost:12345/s/test1/app/management/insightsAndAlerting/triggersActions/rule/1", }, "actionTypeId": "test", + "ruleName": "name-of-alert", "ruleUrl": Object { "absoluteUrl": "http://localhost:12345/s/test1/app/management/insightsAndAlerting/triggersActions/rule/1", "basePathname": "", @@ -2060,6 +2062,7 @@ describe('Execution Handler', () => { "val": "rule url: http://localhost:12345/basePath/s/test1/app/test/rule/1?start=30000&end=90000", }, "actionTypeId": "test", + "ruleName": "name-of-alert", "ruleUrl": Object { "absoluteUrl": "http://localhost:12345/basePath/s/test1/app/test/rule/1?start=30000&end=90000", "basePathname": "/basePath", @@ -2095,6 +2098,7 @@ describe('Execution Handler', () => { "val": "rule url: http://localhost:12345/app/management/insightsAndAlerting/triggersActions/rule/1", }, "actionTypeId": "test", + "ruleName": "name-of-alert", "ruleUrl": Object { "absoluteUrl": "http://localhost:12345/app/management/insightsAndAlerting/triggersActions/rule/1", "basePathname": "", @@ -2127,6 +2131,7 @@ describe('Execution Handler', () => { "val": "rule url: http://localhost:12345/s/test1/app/management/insightsAndAlerting/triggersActions/rule/1", }, "actionTypeId": "test", + "ruleName": "name-of-alert", "ruleUrl": Object { "absoluteUrl": "http://localhost:12345/s/test1/app/management/insightsAndAlerting/triggersActions/rule/1", "basePathname": "", @@ -2159,6 +2164,7 @@ describe('Execution Handler', () => { "val": "rule url: ", }, "actionTypeId": "test", + "ruleName": "name-of-alert", "ruleUrl": undefined, }, ] @@ -2188,6 +2194,7 @@ describe('Execution Handler', () => { "val": "rule url: ", }, "actionTypeId": "test", + "ruleName": "name-of-alert", "ruleUrl": undefined, }, ] @@ -2217,6 +2224,7 @@ describe('Execution Handler', () => { "val": "rule url: ", }, "actionTypeId": "test", + "ruleName": "name-of-alert", "ruleUrl": undefined, }, ] @@ -2249,6 +2257,7 @@ describe('Execution Handler', () => { "val": "rule url: http://localhost:12345/s/test1/app/management/some/other/place", }, "actionTypeId": "test", + "ruleName": "name-of-alert", "ruleUrl": Object { "absoluteUrl": "http://localhost:12345/s/test1/app/management/some/other/place", "basePathname": "", 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 33c4c93abe1116..288a4126c25a25 100644 --- a/x-pack/plugins/alerting/server/task_runner/execution_handler.ts +++ b/x-pack/plugins/alerting/server/task_runner/execution_handler.ts @@ -256,6 +256,7 @@ export class ExecutionHandler< params: injectActionParams({ actionTypeId, ruleUrl, + ruleName: this.rule.name, actionParams: transformSummaryActionParams({ alerts: summarizedAlerts, rule: this.rule, @@ -296,6 +297,7 @@ export class ExecutionHandler< params: injectActionParams({ actionTypeId, ruleUrl, + ruleName: this.rule.name, actionParams: transformActionParams({ actionsPlugin, alertId: ruleId, diff --git a/x-pack/plugins/alerting/server/task_runner/inject_action_params.test.ts b/x-pack/plugins/alerting/server/task_runner/inject_action_params.test.ts index 9964f5f848b2de..d099c16bf52ed0 100644 --- a/x-pack/plugins/alerting/server/task_runner/inject_action_params.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/inject_action_params.test.ts @@ -8,14 +8,16 @@ import { injectActionParams } from './inject_action_params'; describe('injectActionParams', () => { - test(`passes through when actionTypeId isn't .email`, () => { + test(`passes through when actionTypeId isn't .email or .pagerduty`, () => { const actionParams = { message: 'State: "{{state.value}}", Context: "{{context.value}}"', }; + const result = injectActionParams({ actionParams, actionTypeId: '.server-log', }); + expect(result).toMatchInlineSnapshot(` Object { "message": "State: \\"{{state.value}}\\", Context: \\"{{context.value}}\\"", @@ -55,6 +57,146 @@ describe('injectActionParams', () => { `); }); + test('injects the absoluteUrl to the links when actionTypeId is .pagerduty and there are no links', () => { + const actionParams = { + summary: 'My summary', + }; + + const ruleUrl = { + absoluteUrl: + 'http://localhost:5601/app/management/insightsAndAlerting/triggersActions/rule/1', + kibanaBaseUrl: 'http://localhost:5601', + basePathname: '', + spaceIdSegment: '', + relativePath: '/app/management/insightsAndAlerting/triggersActions/rule/1', + }; + + const result = injectActionParams({ + actionParams, + actionTypeId: '.pagerduty', + ruleUrl, + }); + + expect(result).toMatchInlineSnapshot(` + Object { + "links": Array [ + Object { + "href": "http://localhost:5601/app/management/insightsAndAlerting/triggersActions/rule/1", + "text": "Elastic Rule \\"Unknown\\"", + }, + ], + "summary": "My summary", + } + `); + }); + + test('adds the rule name if the rule is defined when actionTypeId is .pagerduty', () => { + const actionParams = { + summary: 'My summary', + }; + + const ruleUrl = { + absoluteUrl: + 'http://localhost:5601/app/management/insightsAndAlerting/triggersActions/rule/1', + kibanaBaseUrl: 'http://localhost:5601', + basePathname: '', + spaceIdSegment: '', + relativePath: '/app/management/insightsAndAlerting/triggersActions/rule/1', + }; + + const result = injectActionParams({ + actionParams, + actionTypeId: '.pagerduty', + ruleUrl, + ruleName: 'My rule', + }); + + expect(result).toMatchInlineSnapshot(` + Object { + "links": Array [ + Object { + "href": "http://localhost:5601/app/management/insightsAndAlerting/triggersActions/rule/1", + "text": "Elastic Rule \\"My rule\\"", + }, + ], + "summary": "My summary", + } + `); + }); + + test('does not produce a runtime error when the actionTypeId is .pagerduty and the links are not an array', () => { + const actionParams = { + summary: 'My summary', + links: 'error', + }; + + const ruleUrl = { + absoluteUrl: + 'http://localhost:5601/app/management/insightsAndAlerting/triggersActions/rule/1', + kibanaBaseUrl: 'http://localhost:5601', + basePathname: '', + spaceIdSegment: '', + relativePath: '/app/management/insightsAndAlerting/triggersActions/rule/1', + }; + + const result = injectActionParams({ + actionParams, + actionTypeId: '.pagerduty', + ruleUrl, + ruleName: 'My rule', + }); + + expect(result).toMatchInlineSnapshot(` + Object { + "links": Array [ + Object { + "href": "http://localhost:5601/app/management/insightsAndAlerting/triggersActions/rule/1", + "text": "Elastic Rule \\"My rule\\"", + }, + ], + "summary": "My summary", + } + `); + }); + + test('injects the absoluteUrl to the links when actionTypeId is .pagerduty with links', () => { + const actionParams = { + summary: 'My summary', + links: [{ href: 'https://example.com', text: 'My link' }], + }; + + const ruleUrl = { + absoluteUrl: + 'http://localhost:5601/app/management/insightsAndAlerting/triggersActions/rule/1', + kibanaBaseUrl: 'http://localhost:5601', + basePathname: '', + spaceIdSegment: '', + relativePath: '/app/management/insightsAndAlerting/triggersActions/rule/1', + }; + + const result = injectActionParams({ + actionParams, + actionTypeId: '.pagerduty', + ruleUrl, + }); + + expect(result).toMatchInlineSnapshot(` + Object { + "links": Array [ + Object { + "href": "http://localhost:5601/app/management/insightsAndAlerting/triggersActions/rule/1", + "text": "Elastic Rule \\"Unknown\\"", + }, + Object { + "href": "https://example.com", + "text": "My link", + }, + ], + "summary": "My summary", + } + `); + }); + test('injects viewInKibanaPath and viewInKibanaText when actionTypeId is .email with basePathname and spaceId', () => { const actionParams = { body: { @@ -88,16 +230,18 @@ describe('injectActionParams', () => { `); }); - test('injects viewInKibanaPath as empty string when the ruleUrl is undefined', () => { + test('injects viewInKibanaPath as empty string when the ruleUrl is undefined and the actionTypeId is .email', () => { const actionParams = { body: { message: 'State: "{{state.value}}", Context: "{{context.value}}"', }, }; + const result = injectActionParams({ actionParams, actionTypeId: '.email', }); + expect(result).toMatchInlineSnapshot(` Object { "body": Object { @@ -110,4 +254,21 @@ describe('injectActionParams', () => { } `); }); + + test('does not add the rule URL when the absoluteUrl is undefined and the actionTypeId is .pagerduty', () => { + const actionParams = { + summary: 'My summary', + }; + + const result = injectActionParams({ + actionParams, + actionTypeId: '.pagerduty', + }); + + expect(result).toMatchInlineSnapshot(` + Object { + "summary": "My summary", + } + `); + }); }); diff --git a/x-pack/plugins/alerting/server/task_runner/inject_action_params.ts b/x-pack/plugins/alerting/server/task_runner/inject_action_params.ts index 321819188f39a4..65cb7f9e65bad6 100644 --- a/x-pack/plugins/alerting/server/task_runner/inject_action_params.ts +++ b/x-pack/plugins/alerting/server/task_runner/inject_action_params.ts @@ -13,11 +13,13 @@ export interface InjectActionParamsOpts { actionTypeId: string; actionParams: RuleActionParams; ruleUrl?: RuleUrl; + ruleName?: string; } export function injectActionParams({ actionTypeId, actionParams, + ruleName, ruleUrl = {}, }: InjectActionParamsOpts) { // Inject kibanaFooterLink if action type is email. This is used by the email action type @@ -36,6 +38,33 @@ export function injectActionParams({ }; } + if (actionTypeId === '.pagerduty') { + /** + * TODO: Remove and use connector adapters + */ + const path = ruleUrl?.absoluteUrl ?? ''; + + if (path.length === 0) { + return actionParams; + } + + const links = Array.isArray(actionParams.links) ? actionParams.links : []; + + return { + ...actionParams, + links: [ + { + href: path, + text: i18n.translate('xpack.alerting.injectActionParams.pagerduty.kibanaLinkText', { + defaultMessage: 'Elastic Rule "{ruleName}"', + values: { ruleName: ruleName ?? 'Unknown' }, + }), + }, + ...links, + ], + }; + } + // Fallback, return action params unchanged return actionParams; } diff --git a/x-pack/plugins/apm/kibana.jsonc b/x-pack/plugins/apm/kibana.jsonc index 61ba6be7433709..858cd142f399cf 100644 --- a/x-pack/plugins/apm/kibana.jsonc +++ b/x-pack/plugins/apm/kibana.jsonc @@ -1,7 +1,7 @@ { "type": "plugin", "id": "@kbn/apm-plugin", - "owner": "@elastic/apm-ui", + "owner": "@elastic/obs-ux-infra_services-team", "description": "The user interface for Elastic APM", "plugin": { "id": "apm", diff --git a/x-pack/plugins/apm/public/components/app/mobile/service_overview/stats/stats.tsx b/x-pack/plugins/apm/public/components/app/mobile/service_overview/stats/stats.tsx index a0caf4b3002ac8..85e29bbe53e399 100644 --- a/x-pack/plugins/apm/public/components/app/mobile/service_overview/stats/stats.tsx +++ b/x-pack/plugins/apm/public/components/app/mobile/service_overview/stats/stats.tsx @@ -7,9 +7,9 @@ import { MetricDatum, MetricTrendShape } from '@elastic/charts'; import { i18n } from '@kbn/i18n'; import { - EuiIcon, EuiFlexGroup, EuiFlexItem, + EuiIcon, EuiLoadingSpinner, } from '@elastic/eui'; import React, { useCallback } from 'react'; @@ -17,9 +17,9 @@ import { useTheme } from '@kbn/observability-shared-plugin/public'; import { NOT_AVAILABLE_LABEL } from '../../../../../../common/i18n'; import { useAnyOfApmParams } from '../../../../../hooks/use_apm_params'; import { - useFetcher, FETCH_STATUS, isPending, + useFetcher, } from '../../../../../hooks/use_fetcher'; import { MetricItem } from './metric_item'; import { usePreviousPeriodLabel } from '../../../../../hooks/use_previous_period_text'; @@ -120,17 +120,20 @@ export function MobileStats({ trendShape: MetricTrendShape.Area, }, { - color: euiTheme.eui.euiColorDisabled, + color: euiTheme.eui.euiColorLightestShade, title: i18n.translate('xpack.apm.mobile.metrics.load.time', { - defaultMessage: 'Slowest App load time', - }), - subtitle: i18n.translate('xpack.apm.mobile.coming.soon', { - defaultMessage: 'Coming Soon', + defaultMessage: 'Average app load time', }), icon: getIcon('visGauge'), - value: 'N/A', - valueFormatter: (value: number) => valueFormatter(value, 's'), - trend: [], + value: data?.currentPeriod?.launchTimes?.value ?? NaN, + valueFormatter: (value: number) => + Number.isNaN(value) + ? NOT_AVAILABLE_LABEL + : valueFormatter(Number(value.toFixed(1)), 'ms'), + trend: data?.currentPeriod?.launchTimes?.timeseries, + extra: getComparisonValueFormatter( + data?.previousPeriod.launchTimes?.value?.toFixed(1) + ), trendShape: MetricTrendShape.Area, }, { diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/accordion_waterfall.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/accordion_waterfall.tsx index 1aa7c9ecb127b3..f705244d6963ed 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/accordion_waterfall.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/accordion_waterfall.tsx @@ -12,11 +12,13 @@ import { EuiFlexItem, EuiIcon, EuiText, + EuiToolTip, } from '@elastic/eui'; import { euiStyled } from '@kbn/kibana-react-plugin/common'; import { groupBy } from 'lodash'; import { transparentize } from 'polished'; import React, { useState } from 'react'; +import { asBigNumber } from '../../../../../../../common/utils/formatters'; import { getCriticalPath } from '../../../../../../../common/critical_path/get_critical_path'; import { useTheme } from '../../../../../../hooks/use_theme'; import { Margins } from '../../../../../shared/charts/timeline'; @@ -148,7 +150,7 @@ export function AccordionWaterfall(props: AccordionWaterfallProps) { @@ -200,12 +202,12 @@ export function AccordionWaterfall(props: AccordionWaterfallProps) { function ToggleAccordionButton({ show, isOpen, - childrenAmount, + childrenCount, onClick, }: { show: boolean; isOpen: boolean; - childrenAmount: number; + childrenCount: number; onClick: () => void; }) { if (!show) { @@ -213,7 +215,12 @@ function ToggleAccordionButton({ } return ( -
+
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events */} @@ -226,8 +233,18 @@ function ToggleAccordionButton({
- - {childrenAmount} + +
+ + {asBigNumber(childrenCount)} + +
diff --git a/x-pack/plugins/apm/server/routes/mobile/get_mobile_average_launch_time.ts b/x-pack/plugins/apm/server/routes/mobile/get_mobile_average_launch_time.ts new file mode 100644 index 00000000000000..e711d5c72fb3bd --- /dev/null +++ b/x-pack/plugins/apm/server/routes/mobile/get_mobile_average_launch_time.ts @@ -0,0 +1,161 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ProcessorEvent } from '@kbn/observability-plugin/common'; +import { + kqlQuery, + rangeQuery, + termQuery, +} from '@kbn/observability-plugin/server'; +import { offsetPreviousPeriodCoordinates } from '../../../common/utils/offset_previous_period_coordinate'; +import { APP_LAUNCH_TIME, SERVICE_NAME } from '../../../common/es_fields/apm'; +import { environmentQuery } from '../../../common/utils/environment_query'; +import { getOffsetInMs } from '../../../common/utils/get_offset_in_ms'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; +import { getBucketSize } from '../../../common/utils/get_bucket_size'; +import { Coordinate } from '../../../typings/timeseries'; +import { Maybe } from '../../../typings/common'; + +export interface AvgLaunchTimeTimeseries { + currentPeriod: { timeseries: Coordinate[]; value: Maybe }; + previousPeriod: { timeseries: Coordinate[]; value: Maybe }; +} + +interface Props { + apmEventClient: APMEventClient; + serviceName: string; + transactionName?: string; + environment: string; + start: number; + end: number; + kuery: string; + offset?: string; +} + +async function getAvgLaunchTimeTimeseries({ + apmEventClient, + serviceName, + transactionName, + environment, + start, + end, + kuery, + offset, +}: Props) { + const { startWithOffset, endWithOffset } = getOffsetInMs({ + start, + end, + offset, + }); + + const { intervalString } = getBucketSize({ + start: startWithOffset, + end: endWithOffset, + minBucketSize: 60, + }); + + const aggs = { + launchTimeAvg: { + avg: { field: APP_LAUNCH_TIME }, + }, + }; + + const response = await apmEventClient.search('get_mobile_launch_time', { + apm: { + events: [ProcessorEvent.metric], + }, + body: { + track_total_hits: false, + size: 0, + query: { + bool: { + filter: [ + ...termQuery(SERVICE_NAME, serviceName), + ...rangeQuery(startWithOffset, endWithOffset), + ...environmentQuery(environment), + ...kqlQuery(kuery), + ], + }, + }, + aggs: { + timeseries: { + date_histogram: { + field: '@timestamp', + fixed_interval: intervalString, + min_doc_count: 0, + extended_bounds: { min: startWithOffset, max: endWithOffset }, + }, + aggs, + }, + ...aggs, + }, + }, + }); + + const timeseries = + response?.aggregations?.timeseries.buckets.map((bucket) => { + return { + x: bucket.key, + y: bucket.launchTimeAvg.value, + }; + }) ?? []; + + return { + timeseries, + value: response.aggregations?.launchTimeAvg?.value, + }; +} + +export async function getMobileAvgLaunchTime({ + kuery, + apmEventClient, + serviceName, + transactionName, + environment, + start, + end, + offset, +}: Props): Promise { + const options = { + serviceName, + transactionName, + apmEventClient, + kuery, + environment, + }; + + const currentPeriodPromise = getAvgLaunchTimeTimeseries({ + ...options, + start, + end, + }); + + const previousPeriodPromise = offset + ? getAvgLaunchTimeTimeseries({ + ...options, + start, + end, + offset, + }) + : { timeseries: [], value: null }; + + const [currentPeriod, previousPeriod] = await Promise.all([ + currentPeriodPromise, + previousPeriodPromise, + ]); + + return { + currentPeriod, + previousPeriod: { + timeseries: offsetPreviousPeriodCoordinates({ + currentPeriodTimeseries: currentPeriod.timeseries, + previousPeriodTimeseries: previousPeriod.timeseries, + }), + value: previousPeriod?.value, + }, + }; +} diff --git a/x-pack/plugins/apm/server/routes/mobile/get_mobile_stats.ts b/x-pack/plugins/apm/server/routes/mobile/get_mobile_stats.ts index 071487298ab7a2..116117426405c2 100644 --- a/x-pack/plugins/apm/server/routes/mobile/get_mobile_stats.ts +++ b/x-pack/plugins/apm/server/routes/mobile/get_mobile_stats.ts @@ -10,16 +10,19 @@ import { getOffsetInMs } from '../../../common/utils/get_offset_in_ms'; import { getMobileSessions } from './get_mobile_sessions'; import { getMobileHttpRequests } from './get_mobile_http_requests'; import { getMobileCrashRate } from './get_mobile_crash_rate'; +import { getMobileAvgLaunchTime } from './get_mobile_average_launch_time'; import { Maybe } from '../../../typings/common'; export interface Timeseries { x: number; y: number; } + interface MobileStats { sessions: { timeseries: Timeseries[]; value: Maybe }; requests: { timeseries: Timeseries[]; value: Maybe }; crashRate: { timeseries: Timeseries[]; value: Maybe }; + launchTimes: { timeseries: Timeseries[]; value: Maybe }; } export interface MobilePeriodStats { @@ -62,10 +65,11 @@ async function getMobileStats({ offset, }; - const [sessions, httpRequests, crashes] = await Promise.all([ + const [sessions, httpRequests, crashes, launchTimeAvg] = await Promise.all([ getMobileSessions({ ...commonProps }), getMobileHttpRequests({ ...commonProps }), getMobileCrashRate({ ...commonProps }), + getMobileAvgLaunchTime({ ...commonProps }), ]); return { @@ -89,6 +93,10 @@ async function getMobileStats({ }; }) as Timeseries[], }, + launchTimes: { + value: launchTimeAvg.currentPeriod.value, + timeseries: launchTimeAvg.currentPeriod.timeseries as Timeseries[], + }, }; } @@ -123,6 +131,7 @@ export async function getMobileStatsPeriods({ sessions: { timeseries: [], value: null }, requests: { timeseries: [], value: null }, crashRate: { timeseries: [], value: null }, + launchTimes: { timeseries: [], value: null }, }; const [currentPeriod, previousPeriod] = await Promise.all([ diff --git a/x-pack/plugins/apm_data_access/kibana.jsonc b/x-pack/plugins/apm_data_access/kibana.jsonc index d0ee0befda1010..ede5cd53c9a6c8 100644 --- a/x-pack/plugins/apm_data_access/kibana.jsonc +++ b/x-pack/plugins/apm_data_access/kibana.jsonc @@ -1,7 +1,7 @@ { "type": "plugin", "id": "@kbn/apm-data-access-plugin", - "owner": "@elastic/apm-ui", + "owner": ["@elastic/obs-knowledge-team", "@elastic/obs-ux-infra_services-team"], "plugin": { "id": "apmDataAccess", "server": true, diff --git a/x-pack/plugins/asset_manager/kibana.jsonc b/x-pack/plugins/asset_manager/kibana.jsonc index b3fcd1b3a4fa1e..1ee4b12d55ea75 100644 --- a/x-pack/plugins/asset_manager/kibana.jsonc +++ b/x-pack/plugins/asset_manager/kibana.jsonc @@ -1,7 +1,7 @@ { "type": "plugin", "id": "@kbn/assetManager-plugin", - "owner": "@elastic/infra-monitoring-ui", + "owner": "@elastic/obs-knowledge-team", "description": "Asset manager plugin for entity assets (inventory, topology, etc)", "plugin": { "id": "assetManager", 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 c5f3f5093aa334..28beb03812aa56 100644 --- a/x-pack/plugins/cases/common/types/api/case/v1.ts +++ b/x-pack/plugins/cases/common/types/api/case/v1.ts @@ -135,6 +135,27 @@ export const CasePostRequestRt = rt.intersection([ ), ]); +/** + * Bulk create cases + */ + +const CaseCreateRequestWithOptionalId = rt.intersection([ + CasePostRequestRt, + rt.exact(rt.partial({ id: rt.string })), +]); + +export const BulkCreateCasesRequestRt = rt.strict({ + cases: rt.array(CaseCreateRequestWithOptionalId), +}); + +export const BulkCreateCasesResponseRt = rt.strict({ + cases: rt.array(CaseRt), +}); + +/** + * Find cases + */ + export const CasesFindRequestSearchFieldsRt = rt.keyof({ description: null, title: null, @@ -480,3 +501,5 @@ export type CasesBulkGetRequest = rt.TypeOf; export type CasesBulkGetResponse = rt.TypeOf; export type GetRelatedCasesByAlertResponse = rt.TypeOf; export type CaseRequestCustomFields = rt.TypeOf; +export type BulkCreateCasesRequest = rt.TypeOf; +export type BulkCreateCasesResponse = rt.TypeOf; diff --git a/x-pack/plugins/cases/docs/openapi/bundled.json b/x-pack/plugins/cases/docs/openapi/bundled.json index 80cdefd30e957e..54a9c31d343125 100644 --- a/x-pack/plugins/cases/docs/openapi/bundled.json +++ b/x-pack/plugins/cases/docs/openapi/bundled.json @@ -1206,8 +1206,11 @@ "$ref": "#/components/schemas/case_response_properties" }, "examples": { - "getCaseResponse": { + "getDefaultCaseResponse": { "$ref": "#/components/examples/get_case_response" + }, + "getDefaultObservabilityCaseReponse": { + "$ref": "#/components/examples/get_case_observability_response" } } } @@ -3078,6 +3081,9 @@ "examples": { "getCaseResponse": { "$ref": "#/components/examples/get_case_response" + }, + "getObservabilityCaseReponse": { + "$ref": "#/components/examples/get_case_observability_response" } } } @@ -4614,13 +4620,16 @@ ], "properties": { "alertId": { - "type": "string", - "example": "6b24c4dc44bc720cfc92797f3d61fff952f2b2627db1fb4f8cc49f4530c4ff42" + "type": "array", + "items": { + "type": "string", + "example": "a6e12ac4-7bce-457b-84f6-d7ce8deb8446" + } }, "created_at": { "type": "string", "format": "date-time", - "example": "2022-03-24T02:31:03.210Z" + "example": "2023-11-06T19:29:38.424Z" }, "created_by": { "type": "object", @@ -4656,8 +4665,11 @@ "example": "73362370-ab1a-11ec-985f-97e55adae8b9" }, "index": { - "type": "string", - "example": ".internal.alerts-security.alerts-default-000001" + "type": "array", + "items": { + "type": "string", + "example": ".internal.alerts-security.alerts-default-000001" + } }, "owner": { "$ref": "#/components/schemas/owners" @@ -4723,10 +4735,11 @@ "updated_at": { "type": "string", "format": "date-time", - "example": null + "nullable": true }, "updated_by": { "type": "object", + "nullable": true, "required": [ "email", "full_name", @@ -6971,6 +6984,7 @@ "syncAlerts": true }, "owner": "cases", + "category": null, "customFields": [ { "type": "text", @@ -7017,6 +7031,104 @@ "external_service": null } }, + "get_case_observability_response": { + "summary": "Retrieves information about an Observability case including its alerts and comments.", + "value": { + "description": "An Observability case description.", + "owner": "observability", + "settings": { + "syncAlerts": false + }, + "tags": [ + "observability", + "tag 1" + ], + "title": "Observability case title 1", + "category": null, + "customFields": [], + "assignees": [ + { + "uid": "u_0wpfV1MqYDaXzLtRVY-gLMrddKDEmfz51Fszhj7hWC8_0" + } + ], + "connector": { + "id": "none", + "type": ".none", + "fields": null, + "name": "none" + }, + "severity": "low", + "status": "in-progress", + "duration": null, + "closed_at": null, + "closed_by": null, + "created_at": "2023-11-06T19:29:04.086Z", + "created_by": { + "username": "elastic", + "full_name": null, + "email": null + }, + "updated_at": "2023-11-06T19:47:55.662Z", + "updated_by": { + "username": "elastic", + "full_name": null, + "email": null, + "profile_uid": "u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0" + }, + "external_service": null, + "id": "c3ff7550-def1-4e90-b6bc-c9969a4a09b1", + "version": "WzI0NywyXQ==", + "totalComment": 1, + "totalAlerts": 1, + "comments": [ + { + "alertId": [ + "a6e12ac4-7bce-457b-84f6-d7ce8deb8446" + ], + "index": [ + ".internal.alerts-observability.logs.alerts-default-000001" + ], + "type": "alert", + "rule": { + "id": "03e4eb87-62ca-4e5d-9570-3d7625e9669d", + "name": "Observability rule" + }, + "owner": "observability", + "created_at": "2023-11-06T19:29:38.424Z", + "created_by": { + "email": null, + "full_name": null, + "username": "elastic", + "profile_uid": "u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0" + }, + "pushed_at": null, + "pushed_by": null, + "updated_at": null, + "updated_by": null, + "id": "59d438d0-79a9-4864-8d4b-e63adacebf6e", + "version": "WzY3LDJd" + }, + { + "comment": "The first comment.", + "type": "user", + "owner": "observability", + "created_at": "2023-11-06T19:29:57.812Z", + "created_by": { + "email": null, + "full_name": null, + "username": "elastic", + "profile_uid": "u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0" + }, + "pushed_at": null, + "pushed_by": null, + "updated_at": null, + "updated_by": null, + "id": "d99342d3-3aa3-4b80-90ec-a702607604f5", + "version": "WzcyLDJd" + } + ] + } + }, "get_case_alerts_response": { "summary": "Retrieves all alerts attached to a case", "value": [ diff --git a/x-pack/plugins/cases/docs/openapi/bundled.yaml b/x-pack/plugins/cases/docs/openapi/bundled.yaml index ec5c1db337f295..8347d7d85741be 100644 --- a/x-pack/plugins/cases/docs/openapi/bundled.yaml +++ b/x-pack/plugins/cases/docs/openapi/bundled.yaml @@ -811,8 +811,10 @@ paths: schema: $ref: '#/components/schemas/case_response_properties' examples: - getCaseResponse: + getDefaultCaseResponse: $ref: '#/components/examples/get_case_response' + getDefaultObservabilityCaseReponse: + $ref: '#/components/examples/get_case_observability_response' '401': description: Authorization information is missing or invalid. content: @@ -2017,6 +2019,8 @@ paths: examples: getCaseResponse: $ref: '#/components/examples/get_case_response' + getObservabilityCaseReponse: + $ref: '#/components/examples/get_case_observability_response' '401': description: Authorization information is missing or invalid. content: @@ -3068,12 +3072,14 @@ components: - type properties: alertId: - type: string - example: 6b24c4dc44bc720cfc92797f3d61fff952f2b2627db1fb4f8cc49f4530c4ff42 + type: array + items: + type: string + example: a6e12ac4-7bce-457b-84f6-d7ce8deb8446 created_at: type: string format: date-time - example: '2022-03-24T02:31:03.210Z' + example: '2023-11-06T19:29:38.424Z' created_by: type: object required: @@ -3100,8 +3106,10 @@ components: type: string example: 73362370-ab1a-11ec-985f-97e55adae8b9 index: - type: string - example: .internal.alerts-security.alerts-default-000001 + type: array + items: + type: string + example: .internal.alerts-security.alerts-default-000001 owner: $ref: '#/components/schemas/owners' pushed_at: @@ -3151,9 +3159,10 @@ components: updated_at: type: string format: date-time - example: null + nullable: true updated_by: type: object + nullable: true required: - email - full_name @@ -4769,6 +4778,7 @@ components: settings: syncAlerts: true owner: cases + category: null customFields: - type: text key: d312efda-ec2b-42ec-9e2c-84981795c581 @@ -4802,6 +4812,84 @@ components: type: .none fields: null external_service: null + get_case_observability_response: + summary: Retrieves information about an Observability case including its alerts and comments. + value: + description: An Observability case description. + owner: observability + settings: + syncAlerts: false + tags: + - observability + - tag 1 + title: Observability case title 1 + category: null + customFields: [] + assignees: + - uid: u_0wpfV1MqYDaXzLtRVY-gLMrddKDEmfz51Fszhj7hWC8_0 + connector: + id: none + type: .none + fields: null + name: none + severity: low + status: in-progress + duration: null + closed_at: null + closed_by: null + created_at: '2023-11-06T19:29:04.086Z' + created_by: + username: elastic + full_name: null + email: null + updated_at: '2023-11-06T19:47:55.662Z' + updated_by: + username: elastic + full_name: null + email: null + profile_uid: u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0 + external_service: null + id: c3ff7550-def1-4e90-b6bc-c9969a4a09b1 + version: WzI0NywyXQ== + totalComment: 1 + totalAlerts: 1 + comments: + - alertId: + - a6e12ac4-7bce-457b-84f6-d7ce8deb8446 + index: + - .internal.alerts-observability.logs.alerts-default-000001 + type: alert + rule: + id: 03e4eb87-62ca-4e5d-9570-3d7625e9669d + name: Observability rule + owner: observability + created_at: '2023-11-06T19:29:38.424Z' + created_by: + email: null + full_name: null + username: elastic + profile_uid: u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0 + pushed_at: null + pushed_by: null + updated_at: null + updated_by: null + id: 59d438d0-79a9-4864-8d4b-e63adacebf6e + version: WzY3LDJd + - comment: The first comment. + type: user + owner: observability + created_at: '2023-11-06T19:29:57.812Z' + created_by: + email: null + full_name: null + username: elastic + profile_uid: u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0 + pushed_at: null + pushed_by: null + updated_at: null + updated_by: null + id: d99342d3-3aa3-4b80-90ec-a702607604f5 + version: WzcyLDJd get_case_alerts_response: summary: Retrieves all alerts attached to a case value: diff --git a/x-pack/plugins/cases/docs/openapi/components/examples/get_case_observability_response.yaml b/x-pack/plugins/cases/docs/openapi/components/examples/get_case_observability_response.yaml new file mode 100644 index 00000000000000..49b9aa692a335a --- /dev/null +++ b/x-pack/plugins/cases/docs/openapi/components/examples/get_case_observability_response.yaml @@ -0,0 +1,97 @@ +summary: Retrieves information about an Observability case including its alerts and comments. +value: + { + "description": "An Observability case description.", + "owner": "observability", + "settings": { + "syncAlerts": false + }, + "tags": [ + "observability", + "tag 1" + ], + "title": "Observability case title 1", + "category": null, + "customFields": [], + "assignees": [ + { + "uid": "u_0wpfV1MqYDaXzLtRVY-gLMrddKDEmfz51Fszhj7hWC8_0" + } + ], + "connector": { + "id": "none", + "type": ".none", + "fields": null, + "name": "none" + }, + "severity": "low", + "status": "in-progress", + "duration": null, + "closed_at": null, + "closed_by": null, + "created_at": "2023-11-06T19:29:04.086Z", + "created_by": { + "username": "elastic", + "full_name": null, + "email": null + }, + "updated_at": "2023-11-06T19:47:55.662Z", + "updated_by": { + "username": "elastic", + "full_name": null, + "email": null, + "profile_uid": "u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0" + }, + "external_service": null, + "id": "c3ff7550-def1-4e90-b6bc-c9969a4a09b1", + "version": "WzI0NywyXQ==", + "totalComment": 1, + "totalAlerts": 1, + "comments": [ + { + "alertId": [ + "a6e12ac4-7bce-457b-84f6-d7ce8deb8446" + ], + "index": [ + ".internal.alerts-observability.logs.alerts-default-000001" + ], + "type": "alert", + "rule": { + "id": "03e4eb87-62ca-4e5d-9570-3d7625e9669d", + "name": "Observability rule" + }, + "owner": "observability", + "created_at": "2023-11-06T19:29:38.424Z", + "created_by": { + "email": null, + "full_name": null, + "username": "elastic", + "profile_uid": "u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0" + }, + "pushed_at": null, + "pushed_by": null, + "updated_at": null, + "updated_by": null, + "id": "59d438d0-79a9-4864-8d4b-e63adacebf6e", + "version": "WzY3LDJd" + }, + { + "comment": "The first comment.", + "type": "user", + "owner": "observability", + "created_at": "2023-11-06T19:29:57.812Z", + "created_by": { + "email": null, + "full_name": null, + "username": "elastic", + "profile_uid": "u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0" + }, + "pushed_at": null, + "pushed_by": null, + "updated_at": null, + "updated_by": null, + "id": "d99342d3-3aa3-4b80-90ec-a702607604f5", + "version": "WzcyLDJd" + } + ] +} \ No newline at end of file diff --git a/x-pack/plugins/cases/docs/openapi/components/examples/get_case_response.yaml b/x-pack/plugins/cases/docs/openapi/components/examples/get_case_response.yaml index d4fc3db97a169a..50dabd2dc8a9b2 100644 --- a/x-pack/plugins/cases/docs/openapi/components/examples/get_case_response.yaml +++ b/x-pack/plugins/cases/docs/openapi/components/examples/get_case_response.yaml @@ -27,6 +27,7 @@ value: "tags":["tag 1"], "settings":{"syncAlerts":true}, "owner":"cases", + "category":null, "customFields": [ { "type": "text", diff --git a/x-pack/plugins/cases/docs/openapi/components/schemas/alert_comment_response_properties.yaml b/x-pack/plugins/cases/docs/openapi/components/schemas/alert_comment_response_properties.yaml index aa39aad1381a02..443d9dcc55523c 100644 --- a/x-pack/plugins/cases/docs/openapi/components/schemas/alert_comment_response_properties.yaml +++ b/x-pack/plugins/cases/docs/openapi/components/schemas/alert_comment_response_properties.yaml @@ -4,12 +4,14 @@ required: - type properties: alertId: - type: string - example: 6b24c4dc44bc720cfc92797f3d61fff952f2b2627db1fb4f8cc49f4530c4ff42 + type: array + items: + type: string + example: a6e12ac4-7bce-457b-84f6-d7ce8deb8446 created_at: type: string format: date-time - example: 2022-03-24T02:31:03.210Z + example: 2023-11-06T19:29:38.424Z created_by: type: object required: @@ -22,8 +24,10 @@ properties: type: string example: 73362370-ab1a-11ec-985f-97e55adae8b9 index: - type: string - example: .internal.alerts-security.alerts-default-000001 + type: array + items: + type: string + example: .internal.alerts-security.alerts-default-000001 owner: $ref: 'owners.yaml' pushed_at: @@ -52,9 +56,10 @@ properties: updated_at: type: string format: date-time - example: null + nullable: true updated_by: type: object + nullable: true required: - email - full_name diff --git a/x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}.yaml b/x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}.yaml index 9629049c9b3428..c5b52a52b741f5 100644 --- a/x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}.yaml +++ b/x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}.yaml @@ -18,8 +18,10 @@ get: schema: $ref: '../components/schemas/case_response_properties.yaml' examples: - getCaseResponse: + getDefaultCaseResponse: $ref: '../components/examples/get_case_response.yaml' + getDefaultObservabilityCaseReponse: + $ref: '../components/examples/get_case_observability_response.yaml' '401': description: Authorization information is missing or invalid. content: diff --git a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}.yaml b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}.yaml index 32e3434f15adda..aecd3f6394bb7f 100644 --- a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}.yaml +++ b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}.yaml @@ -21,6 +21,8 @@ get: examples: getCaseResponse: $ref: '../components/examples/get_case_response.yaml' + getObservabilityCaseReponse: + $ref: '../components/examples/get_case_observability_response.yaml' '401': description: Authorization information is missing or invalid. content: diff --git a/x-pack/plugins/cases/public/common/mock/test_providers.tsx b/x-pack/plugins/cases/public/common/mock/test_providers.tsx index 7dfed5d7188de3..846ae8172b3277 100644 --- a/x-pack/plugins/cases/public/common/mock/test_providers.tsx +++ b/x-pack/plugins/cases/public/common/mock/test_providers.tsx @@ -11,6 +11,7 @@ import React from 'react'; import { MemoryRouter } from 'react-router-dom'; import { ThemeProvider } from 'styled-components'; +import { render as reactRender } from '@testing-library/react'; import type { RenderOptions, RenderResult } from '@testing-library/react'; import type { ILicense } from '@kbn/licensing-plugin/public'; import type { ScopedFilesClient } from '@kbn/files-plugin/public'; @@ -20,7 +21,6 @@ import { I18nProvider } from '@kbn/i18n-react'; import { createMockFilesClient } from '@kbn/shared-ux-file-mocks'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { render as reactRender } from '@testing-library/react'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { FilesContext } from '@kbn/shared-ux-file-context'; diff --git a/x-pack/plugins/cases/public/common/use_cases_toast.test.tsx b/x-pack/plugins/cases/public/common/use_cases_toast.test.tsx index d6597e31362e76..53fe9118a6aa07 100644 --- a/x-pack/plugins/cases/public/common/use_cases_toast.test.tsx +++ b/x-pack/plugins/cases/public/common/use_cases_toast.test.tsx @@ -14,7 +14,7 @@ import { alertComment, basicComment, mockCase } from '../containers/mock'; import React from 'react'; import userEvent from '@testing-library/user-event'; import type { SupportedCaseAttachment } from '../types'; -import { getByTestId } from '@testing-library/dom'; +import { getByTestId } from '@testing-library/react'; import { OWNER_INFO } from '../../common/constants'; jest.mock('./lib/kibana'); diff --git a/x-pack/plugins/cases/public/components/all_cases/selector_modal/use_cases_add_to_existing_case_modal.test.tsx b/x-pack/plugins/cases/public/components/all_cases/selector_modal/use_cases_add_to_existing_case_modal.test.tsx index 2cfaacc8383ca1..a80e1935d1addc 100644 --- a/x-pack/plugins/cases/public/components/all_cases/selector_modal/use_cases_add_to_existing_case_modal.test.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/selector_modal/use_cases_add_to_existing_case_modal.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { waitFor } from '@testing-library/dom'; +import { waitFor } from '@testing-library/react'; import { act, renderHook } from '@testing-library/react-hooks'; import userEvent from '@testing-library/user-event'; import React from 'react'; diff --git a/x-pack/plugins/cases/public/components/all_cases/severity_filter.test.tsx b/x-pack/plugins/cases/public/components/all_cases/severity_filter.test.tsx index 30d5c75e63589d..26169e16c5bed1 100644 --- a/x-pack/plugins/cases/public/components/all_cases/severity_filter.test.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/severity_filter.test.tsx @@ -10,7 +10,7 @@ import React from 'react'; import type { AppMockRenderer } from '../../common/mock'; import { createAppMockRenderer } from '../../common/mock'; import userEvent from '@testing-library/user-event'; -import { waitFor } from '@testing-library/dom'; +import { waitFor } from '@testing-library/react'; import { waitForEuiPopoverOpen } from '@elastic/eui/lib/test/rtl'; import { SeverityFilter } from './severity_filter'; diff --git a/x-pack/plugins/cases/public/components/all_cases/use_actions.test.tsx b/x-pack/plugins/cases/public/components/all_cases/use_actions.test.tsx index 0efc7a58628934..ecc9233fc327cb 100644 --- a/x-pack/plugins/cases/public/components/all_cases/use_actions.test.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/use_actions.test.tsx @@ -6,7 +6,7 @@ */ import userEvent from '@testing-library/user-event'; -import { waitFor } from '@testing-library/dom'; +import { waitFor } from '@testing-library/react'; import { renderHook } from '@testing-library/react-hooks/dom'; import { useActions } from './use_actions'; diff --git a/x-pack/plugins/cases/public/components/case_action_bar/actions.test.tsx b/x-pack/plugins/cases/public/components/case_action_bar/actions.test.tsx index c79e88ce350ab6..ef97a5f8d854d2 100644 --- a/x-pack/plugins/cases/public/components/case_action_bar/actions.test.tsx +++ b/x-pack/plugins/cases/public/components/case_action_bar/actions.test.tsx @@ -13,7 +13,7 @@ import { basicCase, basicPush } from '../../containers/mock'; import { Actions } from './actions'; import * as i18n from '../case_view/translations'; import * as api from '../../containers/api'; -import { waitFor } from '@testing-library/dom'; +import { waitFor } from '@testing-library/react'; jest.mock('../../containers/api'); diff --git a/x-pack/plugins/cases/public/components/case_view/components/case_view_alerts.test.tsx b/x-pack/plugins/cases/public/components/case_view/components/case_view_alerts.test.tsx index cba9169590839c..b85dbe7564cf9b 100644 --- a/x-pack/plugins/cases/public/components/case_view/components/case_view_alerts.test.tsx +++ b/x-pack/plugins/cases/public/components/case_view/components/case_view_alerts.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { waitFor } from '@testing-library/dom'; +import { waitFor } from '@testing-library/react'; import { OBSERVABILITY_OWNER } from '../../../../common/constants'; import { alertCommentWithIndices, basicCase } from '../../../containers/mock'; import type { AppMockRenderer } from '../../../common/mock'; diff --git a/x-pack/plugins/cases/public/components/case_view/index.test.tsx b/x-pack/plugins/cases/public/components/case_view/index.test.tsx index 07b58820b52b72..13524989bc7b89 100644 --- a/x-pack/plugins/cases/public/components/case_view/index.test.tsx +++ b/x-pack/plugins/cases/public/components/case_view/index.test.tsx @@ -30,7 +30,7 @@ import { useGetSupportedActionConnectors } from '../../containers/configure/use_ import type { AppMockRenderer } from '../../common/mock'; import { createAppMockRenderer } from '../../common/mock'; import CaseView from '.'; -import { waitFor } from '@testing-library/dom'; +import { waitFor } from '@testing-library/react'; import { useGetTags } from '../../containers/use_get_tags'; import { casesQueriesKeys } from '../../containers/constants'; import { diff --git a/x-pack/plugins/cases/public/components/create/form.test.tsx b/x-pack/plugins/cases/public/components/create/form.test.tsx index e7bd2fc754f347..05e77886248000 100644 --- a/x-pack/plugins/cases/public/components/create/form.test.tsx +++ b/x-pack/plugins/cases/public/components/create/form.test.tsx @@ -7,8 +7,7 @@ import React from 'react'; import { mount } from 'enzyme'; -import { act, render, within, fireEvent } from '@testing-library/react'; -import { waitFor } from '@testing-library/dom'; +import { act, render, within, fireEvent, waitFor } from '@testing-library/react'; import { licensingMock } from '@kbn/licensing-plugin/public/mocks'; import { NONE_CONNECTOR_ID } from '../../../common/constants'; diff --git a/x-pack/plugins/cases/public/components/create/severity.test.tsx b/x-pack/plugins/cases/public/components/create/severity.test.tsx index 328b9b4cd5e00f..bf81dfc357fc73 100644 --- a/x-pack/plugins/cases/public/components/create/severity.test.tsx +++ b/x-pack/plugins/cases/public/components/create/severity.test.tsx @@ -15,7 +15,7 @@ import { Severity } from './severity'; import type { FormProps } from './schema'; import { schema } from './schema'; import userEvent from '@testing-library/user-event'; -import { waitFor } from '@testing-library/dom'; +import { waitFor } from '@testing-library/react'; import { waitForEuiPopoverOpen } from '@elastic/eui/lib/test/rtl'; let globalForm: FormHook; 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 d81e31ba69d6d8..7864361063b311 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/dom'; +import { screen, waitFor } from '@testing-library/react'; import type { AppMockRenderer } from '../../common/mock'; import { createAppMockRenderer } from '../../common/mock'; diff --git a/x-pack/plugins/cases/public/components/experimental_badge/experimental_badge.test.tsx b/x-pack/plugins/cases/public/components/experimental_badge/experimental_badge.test.tsx index a13aea90e8e6ea..38b5960898cf0c 100644 --- a/x-pack/plugins/cases/public/components/experimental_badge/experimental_badge.test.tsx +++ b/x-pack/plugins/cases/public/components/experimental_badge/experimental_badge.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { screen } from '@testing-library/dom'; +import { screen } from '@testing-library/react'; import type { AppMockRenderer } from '../../common/mock'; import { createAppMockRenderer } from '../../common/mock'; diff --git a/x-pack/plugins/cases/public/components/visualizations/actions/add_to_existing_case.test.tsx b/x-pack/plugins/cases/public/components/visualizations/actions/add_to_existing_case.test.tsx index 9c911e5aa89b47..a32ece05eac749 100644 --- a/x-pack/plugins/cases/public/components/visualizations/actions/add_to_existing_case.test.tsx +++ b/x-pack/plugins/cases/public/components/visualizations/actions/add_to_existing_case.test.tsx @@ -24,7 +24,7 @@ import { mockTimeRange, } from './mocks'; import { useKibana } from '../../../common/lib/kibana'; -import { waitFor } from '@testing-library/dom'; +import { waitFor } from '@testing-library/react'; import { canUseCases } from '../../../client/helpers/can_use_cases'; import { getCaseOwnerByAppId } from '../../../../common/utils/owner'; diff --git a/x-pack/plugins/cases/public/components/visualizations/actions/add_to_new_case.test.tsx b/x-pack/plugins/cases/public/components/visualizations/actions/add_to_new_case.test.tsx index f55f95cbfb0eca..3549ca3301d189 100644 --- a/x-pack/plugins/cases/public/components/visualizations/actions/add_to_new_case.test.tsx +++ b/x-pack/plugins/cases/public/components/visualizations/actions/add_to_new_case.test.tsx @@ -8,7 +8,7 @@ import { LENS_EMBEDDABLE_TYPE, type Embeddable as LensEmbeddable } from '@kbn/lens-plugin/public'; import { ErrorEmbeddable } from '@kbn/embeddable-plugin/public'; import type { Action } from '@kbn/ui-actions-plugin/public'; -import { waitFor } from '@testing-library/dom'; +import { waitFor } from '@testing-library/react'; import { createAddToNewCaseLensAction } from './add_to_new_case'; import type { ActionContext } from './types'; diff --git a/x-pack/plugins/cases/public/containers/configure/use_get_case_configuration.test.tsx b/x-pack/plugins/cases/public/containers/configure/use_get_case_configuration.test.tsx index 1cb8685e26cbfd..d625e247cb5725 100644 --- a/x-pack/plugins/cases/public/containers/configure/use_get_case_configuration.test.tsx +++ b/x-pack/plugins/cases/public/containers/configure/use_get_case_configuration.test.tsx @@ -8,7 +8,7 @@ import { renderHook } from '@testing-library/react-hooks'; import { useGetCaseConfiguration } from './use_get_case_configuration'; import * as api from './api'; -import { waitFor } from '@testing-library/dom'; +import { waitFor } from '@testing-library/react'; import { useToasts } from '../../common/lib/kibana'; import type { AppMockRenderer } from '../../common/mock'; import { createAppMockRenderer } from '../../common/mock'; diff --git a/x-pack/plugins/cases/public/containers/use_get_case.test.tsx b/x-pack/plugins/cases/public/containers/use_get_case.test.tsx index 0514acf4a71eab..2b5082a9e22a55 100644 --- a/x-pack/plugins/cases/public/containers/use_get_case.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_get_case.test.tsx @@ -8,7 +8,7 @@ import { renderHook } from '@testing-library/react-hooks'; import { useGetCase } from './use_get_case'; import * as api from './api'; -import { waitFor } from '@testing-library/dom'; +import { waitFor } from '@testing-library/react'; import React from 'react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { useToasts } from '../common/lib/kibana'; diff --git a/x-pack/plugins/cases/public/containers/use_get_feature_ids.test.tsx b/x-pack/plugins/cases/public/containers/use_get_feature_ids.test.tsx index fdec17c215fda4..298abb5d133e78 100644 --- a/x-pack/plugins/cases/public/containers/use_get_feature_ids.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_get_feature_ids.test.tsx @@ -6,7 +6,7 @@ */ import { renderHook } from '@testing-library/react-hooks'; -import { waitFor } from '@testing-library/dom'; +import { waitFor } from '@testing-library/react'; import { useToasts } from '../common/lib/kibana'; import type { AppMockRenderer } from '../common/mock'; import { createAppMockRenderer } from '../common/mock'; diff --git a/x-pack/plugins/cases/server/client/attachments/delete.ts b/x-pack/plugins/cases/server/client/attachments/delete.ts index b21c6b5ae753f1..e67a5788b34766 100644 --- a/x-pack/plugins/cases/server/client/attachments/delete.ts +++ b/x-pack/plugins/cases/server/client/attachments/delete.ts @@ -124,13 +124,15 @@ export async function deleteComment( const attachmentRequestAttributes = decodeOrThrow(AttachmentRequestRt)(attachment.attributes); await userActionService.creator.createUserAction({ - type: UserActionTypes.comment, - action: UserActionActions.delete, - caseId: id, - attachmentId: attachmentID, - payload: { attachment: attachmentRequestAttributes }, - user, - owner: attachment.attributes.owner, + userAction: { + type: UserActionTypes.comment, + action: UserActionActions.delete, + caseId: id, + attachmentId: attachmentID, + payload: { attachment: attachmentRequestAttributes }, + user, + owner: attachment.attributes.owner, + }, }); await handleAlerts({ alertsService, attachments: [attachment.attributes], caseId: id }); 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 new file mode 100644 index 00000000000000..fa0b4e3f584e58 --- /dev/null +++ b/x-pack/plugins/cases/server/client/cases/bulk_create.test.ts @@ -0,0 +1,1231 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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_DESCRIPTION_LENGTH, + MAX_TAGS_PER_CASE, + MAX_LENGTH_PER_TAG, + MAX_TITLE_LENGTH, + MAX_ASSIGNEES_PER_CASE, + MAX_CUSTOM_FIELDS_PER_CASE, +} from '../../../common/constants'; +import type { CasePostRequest } from '../../../common'; +import { SECURITY_SOLUTION_OWNER } from '../../../common'; +import { mockCases } from '../../mocks'; +import { createCasesClientMock, createCasesClientMockArgs } from '../mocks'; +import { bulkCreate } from './bulk_create'; +import { CaseSeverity, ConnectorTypes, CustomFieldTypes } from '../../../common/types/domain'; + +import type { CaseCustomFields } from '../../../common/types/domain'; +import { omit } from 'lodash'; + +jest.mock('@kbn/core-saved-objects-utils-server', () => { + const actual = jest.requireActual('@kbn/core-saved-objects-utils-server'); + + return { + ...actual, + SavedObjectsUtils: { + generateId: () => 'mock-saved-object-id', + }, + }; +}); + +describe('bulkCreate', () => { + const getCases = (overrides = {}) => [ + { + title: 'My Case', + tags: [], + description: 'testing sir', + connector: { + id: '.none', + name: 'None', + type: ConnectorTypes.none, + fields: null, + }, + settings: { syncAlerts: true }, + severity: CaseSeverity.LOW, + owner: SECURITY_SOLUTION_OWNER, + assignees: [{ uid: '1' }], + ...overrides, + }, + ]; + + const caseSO = mockCases[0]; + const casesClientMock = createCasesClientMock(); + casesClientMock.configure.get = jest.fn().mockResolvedValue([]); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('execution', () => { + const createdAtDate = new Date('2023-11-05'); + + beforeAll(() => { + jest.useFakeTimers(); + jest.setSystemTime(createdAtDate); + }); + + afterAll(() => { + jest.useRealTimers(); + }); + + const clientArgs = createCasesClientMockArgs(); + + clientArgs.services.caseService.bulkCreateCases.mockResolvedValue({ + saved_objects: [caseSO], + }); + + it('create the cases correctly', async () => { + clientArgs.services.caseService.bulkCreateCases.mockResolvedValue({ + saved_objects: [ + caseSO, + { ...caseSO, attributes: { ...caseSO.attributes, severity: CaseSeverity.CRITICAL } }, + ], + }); + + const res = await bulkCreate( + { cases: [getCases()[0], getCases({ severity: CaseSeverity.CRITICAL })[0]] }, + clientArgs, + casesClientMock + ); + + expect(res).toMatchInlineSnapshot(` + Object { + "cases": Array [ + Object { + "assignees": Array [], + "category": null, + "closed_at": null, + "closed_by": null, + "comments": Array [], + "connector": Object { + "fields": null, + "id": "none", + "name": "none", + "type": ".none", + }, + "created_at": "2019-11-25T21:54:48.952Z", + "created_by": Object { + "email": "testemail@elastic.co", + "full_name": "elastic", + "username": "elastic", + }, + "customFields": Array [], + "description": "This is a brand new case of a bad meanie defacing data", + "duration": null, + "external_service": null, + "id": "mock-id-1", + "owner": "securitySolution", + "settings": Object { + "syncAlerts": true, + }, + "severity": "low", + "status": "open", + "tags": Array [ + "defacement", + ], + "title": "Super Bad Security Issue", + "totalAlerts": 0, + "totalComment": 0, + "updated_at": "2019-11-25T21:54:48.952Z", + "updated_by": Object { + "email": "testemail@elastic.co", + "full_name": "elastic", + "username": "elastic", + }, + "version": "WzAsMV0=", + }, + Object { + "assignees": Array [], + "category": null, + "closed_at": null, + "closed_by": null, + "comments": Array [], + "connector": Object { + "fields": null, + "id": "none", + "name": "none", + "type": ".none", + }, + "created_at": "2019-11-25T21:54:48.952Z", + "created_by": Object { + "email": "testemail@elastic.co", + "full_name": "elastic", + "username": "elastic", + }, + "customFields": Array [], + "description": "This is a brand new case of a bad meanie defacing data", + "duration": null, + "external_service": null, + "id": "mock-id-1", + "owner": "securitySolution", + "settings": Object { + "syncAlerts": true, + }, + "severity": "critical", + "status": "open", + "tags": Array [ + "defacement", + ], + "title": "Super Bad Security Issue", + "totalAlerts": 0, + "totalComment": 0, + "updated_at": "2019-11-25T21:54:48.952Z", + "updated_by": Object { + "email": "testemail@elastic.co", + "full_name": "elastic", + "username": "elastic", + }, + "version": "WzAsMV0=", + }, + ], + } + `); + }); + + it('accepts an ID in the request correctly', async () => { + await bulkCreate({ cases: getCases({ id: 'my-id' }) }, clientArgs, casesClientMock); + + expect(clientArgs.services.caseService.bulkCreateCases.mock.calls[0][0].cases[0].id).toBe( + 'my-id' + ); + }); + + it('generates an ID if not provided in the request', async () => { + await bulkCreate({ cases: getCases() }, clientArgs, casesClientMock); + + expect(clientArgs.services.caseService.bulkCreateCases.mock.calls[0][0].cases[0].id).toBe( + 'mock-saved-object-id' + ); + }); + + it('calls bulkCreateCases correctly', async () => { + await bulkCreate( + { cases: [getCases()[0], getCases({ severity: CaseSeverity.CRITICAL })[0]] }, + clientArgs, + casesClientMock + ); + + expect(clientArgs.services.caseService.bulkCreateCases.mock.calls[0][0]) + .toMatchInlineSnapshot(` + Object { + "cases": Array [ + Object { + "assignees": Array [ + Object { + "uid": "1", + }, + ], + "category": null, + "closed_at": null, + "closed_by": null, + "connector": Object { + "fields": null, + "id": ".none", + "name": "None", + "type": ".none", + }, + "created_at": "2023-11-05T00:00:00.000Z", + "created_by": Object { + "email": "damaged_raccoon@elastic.co", + "full_name": "Damaged Raccoon", + "profile_uid": "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0", + "username": "damaged_raccoon", + }, + "customFields": Array [], + "description": "testing sir", + "duration": null, + "external_service": null, + "id": "mock-saved-object-id", + "owner": "securitySolution", + "settings": Object { + "syncAlerts": true, + }, + "severity": "low", + "status": "open", + "tags": Array [], + "title": "My Case", + "updated_at": null, + "updated_by": null, + }, + Object { + "assignees": Array [ + Object { + "uid": "1", + }, + ], + "category": null, + "closed_at": null, + "closed_by": null, + "connector": Object { + "fields": null, + "id": ".none", + "name": "None", + "type": ".none", + }, + "created_at": "2023-11-05T00:00:00.000Z", + "created_by": Object { + "email": "damaged_raccoon@elastic.co", + "full_name": "Damaged Raccoon", + "profile_uid": "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0", + "username": "damaged_raccoon", + }, + "customFields": Array [], + "description": "testing sir", + "duration": null, + "external_service": null, + "id": "mock-saved-object-id", + "owner": "securitySolution", + "settings": Object { + "syncAlerts": true, + }, + "severity": "critical", + "status": "open", + "tags": Array [], + "title": "My Case", + "updated_at": null, + "updated_by": null, + }, + ], + "refresh": false, + } + `); + }); + + it('throws an error if bulkCreateCases returns at least one error ', async () => { + clientArgs.services.caseService.bulkCreateCases.mockResolvedValue({ + saved_objects: [ + caseSO, + { + id: '2', + type: 'cases', + error: { + error: 'My error', + message: 'not found', + statusCode: 404, + }, + references: [], + }, + { + id: '3', + type: 'cases', + error: { + error: 'My second error', + message: 'conflict', + statusCode: 409, + }, + references: [], + }, + ], + }); + + await expect(bulkCreate({ cases: getCases() }, clientArgs, casesClientMock)).rejects.toThrow( + `Failed to bulk create cases: Error: My error` + ); + }); + + it('constructs the case error correctly', async () => { + expect.assertions(1); + + clientArgs.services.caseService.bulkCreateCases.mockResolvedValue({ + saved_objects: [ + caseSO, + { + id: '1', + type: 'cases', + error: { + error: 'My error', + message: 'not found', + statusCode: 404, + }, + references: [], + }, + ], + }); + + try { + await bulkCreate({ cases: getCases() }, clientArgs, casesClientMock); + } catch (error) { + expect(error.wrappedError.output).toEqual({ + headers: {}, + payload: { error: 'Not Found', message: 'My error', statusCode: 404 }, + statusCode: 404, + }); + } + }); + }); + + describe('authorization', () => { + const clientArgs = createCasesClientMockArgs(); + + clientArgs.services.caseService.bulkCreateCases.mockResolvedValue({ + saved_objects: [caseSO], + }); + + it('validates the cases correctly', async () => { + await bulkCreate( + { cases: [getCases()[0], getCases({ owner: 'cases' })[0]] }, + clientArgs, + casesClientMock + ); + + expect(clientArgs.authorization.ensureAuthorized).toHaveBeenCalledWith({ + entities: [ + { id: 'mock-saved-object-id', owner: 'securitySolution' }, + { id: 'mock-saved-object-id', owner: 'cases' }, + ], + operation: { + action: 'case_create', + docType: 'case', + ecsType: 'creation', + name: 'createCase', + savedObjectType: 'cases', + verbs: { past: 'created', present: 'create', progressive: 'creating' }, + }, + }); + }); + }); + + describe('Assignees', () => { + const clientArgs = createCasesClientMockArgs(); + + it('notifies single assignees', async () => { + const caseSOWithAssignees = { + ...caseSO, + attributes: { ...caseSO.attributes, assignees: [{ uid: '1' }] }, + }; + + clientArgs.services.caseService.bulkCreateCases.mockResolvedValue({ + saved_objects: [caseSOWithAssignees], + }); + + const cases = getCases(); + + await bulkCreate({ cases }, clientArgs, casesClientMock); + + expect(clientArgs.services.notificationService.bulkNotifyAssignees).toHaveBeenCalledWith([ + { + assignees: cases[0].assignees, + theCase: caseSOWithAssignees, + }, + ]); + }); + + it('notifies multiple assignees', async () => { + const caseSOWithAssignees = { + ...caseSO, + attributes: { ...caseSO.attributes, assignees: [{ uid: '1' }, { uid: '2' }] }, + }; + + clientArgs.services.caseService.bulkCreateCases.mockResolvedValue({ + saved_objects: [caseSOWithAssignees], + }); + + await bulkCreate( + { cases: getCases({ assignees: [{ uid: '1' }, { uid: '2' }] }) }, + clientArgs, + casesClientMock + ); + + expect(clientArgs.services.notificationService.bulkNotifyAssignees).toHaveBeenCalledWith([ + { + assignees: [{ uid: '1' }, { uid: '2' }], + theCase: caseSOWithAssignees, + }, + ]); + }); + + it('does not notify when there are no assignees', async () => { + clientArgs.services.caseService.bulkCreateCases.mockResolvedValue({ + saved_objects: [caseSO], + }); + + await bulkCreate({ cases: getCases({ assignees: [] }) }, clientArgs, casesClientMock); + + expect(clientArgs.services.notificationService.bulkNotifyAssignees).not.toHaveBeenCalled(); + }); + + it('does not notify the current user', async () => { + const caseSOWithAssignees = { + ...caseSO, + attributes: { + ...caseSO.attributes, + assignees: [{ uid: '1' }, { uid: 'u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0' }], + }, + }; + + clientArgs.services.caseService.bulkCreateCases.mockResolvedValue({ + saved_objects: [caseSOWithAssignees], + }); + + await bulkCreate( + { + cases: getCases({ + assignees: [{ uid: '1' }, { uid: 'u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0' }], + }), + }, + clientArgs, + casesClientMock + ); + + expect(clientArgs.services.notificationService.bulkNotifyAssignees).toHaveBeenCalledWith([ + { + assignees: [{ uid: '1' }], + theCase: caseSOWithAssignees, + }, + ]); + }); + + it('should throw an error if the assignees array length is too long', async () => { + const assignees = Array(MAX_ASSIGNEES_PER_CASE + 1).fill({ uid: 'foo' }); + + await expect( + bulkCreate({ cases: getCases({ assignees }) }, clientArgs, casesClientMock) + ).rejects.toThrow( + `Failed to bulk create cases: Error: The length of the field assignees is too long. Array must be of length <= ${MAX_ASSIGNEES_PER_CASE}.` + ); + }); + + it('should throw if the user does not have the correct license', async () => { + clientArgs.services.licensingService.isAtLeastPlatinum.mockResolvedValue(false); + + await expect(bulkCreate({ cases: getCases() }, clientArgs, casesClientMock)).rejects.toThrow( + `Failed to bulk create cases: Error: In order to assign users to cases, you must be subscribed to an Elastic Platinum license` + ); + }); + }); + + describe('Attributes', () => { + const clientArgs = createCasesClientMockArgs(); + clientArgs.services.caseService.bulkCreateCases.mockResolvedValue({ saved_objects: [caseSO] }); + + it('should throw an error when an excess field exists', async () => { + await expect( + bulkCreate({ cases: getCases({ foo: 'bar' }) }, clientArgs, casesClientMock) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Failed to bulk create cases: Error: invalid keys \\"foo\\""` + ); + }); + }); + + describe('title', () => { + const clientArgs = createCasesClientMockArgs(); + clientArgs.services.caseService.bulkCreateCases.mockResolvedValue({ saved_objects: [caseSO] }); + + it(`should not throw an error if the title is non empty and less than ${MAX_TITLE_LENGTH} characters`, async () => { + await expect( + bulkCreate( + { cases: getCases({ title: 'This is a test case!!' }) }, + clientArgs, + casesClientMock + ) + ).resolves.not.toThrow(); + }); + + it('should throw an error if the title length is too long', async () => { + await expect( + bulkCreate( + { + cases: getCases({ + title: + 'This is a very long title with more than one hundred and sixty characters!! To confirm the maximum limit error thrown for more than one hundred and sixty characters!!', + }), + }, + clientArgs, + casesClientMock + ) + ).rejects.toThrow( + `Failed to bulk create cases: Error: The length of the title is too long. The maximum length is ${MAX_TITLE_LENGTH}.` + ); + }); + + it('should throw an error if the title is an empty string', async () => { + await expect( + bulkCreate({ cases: getCases({ title: '' }) }, clientArgs, casesClientMock) + ).rejects.toThrow( + 'Failed to bulk create cases: Error: The title field cannot be an empty string.' + ); + }); + + it('should throw an error if the title is a string with empty characters', async () => { + await expect( + bulkCreate({ cases: getCases({ title: ' ' }) }, clientArgs, casesClientMock) + ).rejects.toThrow( + 'Failed to bulk create cases: Error: The title field cannot be an empty string.' + ); + }); + + it('should trim title', async () => { + await bulkCreate( + { cases: getCases({ title: 'title with spaces ' }) }, + clientArgs, + casesClientMock + ); + + const title = clientArgs.services.caseService.bulkCreateCases.mock.calls[0][0].cases[0].title; + + expect(title).toBe('title with spaces'); + }); + }); + + describe('description', () => { + const clientArgs = createCasesClientMockArgs(); + clientArgs.services.caseService.bulkCreateCases.mockResolvedValue({ saved_objects: [caseSO] }); + + it(`should not throw an error if the description is non empty and less than ${MAX_DESCRIPTION_LENGTH} characters`, async () => { + await expect( + bulkCreate( + { cases: getCases({ description: 'This is a test description!!' }) }, + clientArgs, + casesClientMock + ) + ).resolves.not.toThrow(); + }); + + it('should throw an error if the description length is too long', async () => { + const description = Array(MAX_DESCRIPTION_LENGTH + 1) + .fill('x') + .toString(); + + await expect( + bulkCreate({ cases: getCases({ description }) }, clientArgs, casesClientMock) + ).rejects.toThrow( + `Failed to bulk create cases: Error: The length of the description is too long. The maximum length is ${MAX_DESCRIPTION_LENGTH}.` + ); + }); + + it('should throw an error if the description is an empty string', async () => { + await expect( + bulkCreate({ cases: getCases({ description: '' }) }, clientArgs, casesClientMock) + ).rejects.toThrow( + 'Failed to bulk create cases: Error: The description field cannot be an empty string.' + ); + }); + + it('should throw an error if the description is a string with empty characters', async () => { + await expect( + bulkCreate({ cases: getCases({ description: ' ' }) }, clientArgs, casesClientMock) + ).rejects.toThrow( + 'Failed to bulk create cases: Error: The description field cannot be an empty string.' + ); + }); + + it('should trim description', async () => { + await bulkCreate( + { cases: getCases({ description: 'this is a description with spaces!! ' }) }, + clientArgs, + casesClientMock + ); + + const description = + clientArgs.services.caseService.bulkCreateCases.mock.calls[0][0].cases[0].description; + + expect(description).toBe('this is a description with spaces!!'); + }); + }); + + describe('tags', () => { + const clientArgs = createCasesClientMockArgs(); + clientArgs.services.caseService.bulkCreateCases.mockResolvedValue({ saved_objects: [caseSO] }); + + it('should not throw an error if the tags array is empty', async () => { + await expect( + bulkCreate({ cases: getCases({ tags: [] }) }, clientArgs, casesClientMock) + ).resolves.not.toThrow(); + }); + + it('should not throw an error if the tags array has non empty string within limit', async () => { + await expect( + bulkCreate({ cases: getCases({ tags: ['abc'] }) }, clientArgs, casesClientMock) + ).resolves.not.toThrow(); + }); + + it('should throw an error if the tags array length is too long', async () => { + const tags = Array(MAX_TAGS_PER_CASE + 1).fill('foo'); + + await expect( + bulkCreate({ cases: getCases({ tags }) }, clientArgs, casesClientMock) + ).rejects.toThrow( + `Failed to bulk create cases: Error: The length of the field tags is too long. Array must be of length <= ${MAX_TAGS_PER_CASE}.` + ); + }); + + it('should throw an error if the tags array has empty string', async () => { + await expect( + bulkCreate({ cases: getCases({ tags: [''] }) }, clientArgs, casesClientMock) + ).rejects.toThrow( + 'Failed to bulk create cases: Error: The tag field cannot be an empty string.' + ); + }); + + it('should throw an error if the tags array has string with empty characters', async () => { + await expect( + bulkCreate({ cases: getCases({ tags: [' '] }) }, clientArgs, casesClientMock) + ).rejects.toThrow( + 'Failed to bulk create cases: Error: The tag field cannot be an empty string.' + ); + }); + + it('should throw an error if the tag length is too long', async () => { + const tag = Array(MAX_LENGTH_PER_TAG + 1) + .fill('f') + .toString(); + + await expect( + bulkCreate({ cases: getCases({ tags: [tag] }) }, clientArgs, casesClientMock) + ).rejects.toThrow( + `Failed to bulk create cases: Error: The length of the tag is too long. The maximum length is ${MAX_LENGTH_PER_TAG}.` + ); + }); + + it('should trim tags', async () => { + await bulkCreate( + { cases: getCases({ tags: ['pepsi ', 'coke'] }) }, + clientArgs, + casesClientMock + ); + + const tags = clientArgs.services.caseService.bulkCreateCases.mock.calls[0][0].cases[0].tags; + + expect(tags).toEqual(['pepsi', 'coke']); + }); + }); + + describe('Category', () => { + const clientArgs = createCasesClientMockArgs(); + clientArgs.services.caseService.bulkCreateCases.mockResolvedValue({ saved_objects: [caseSO] }); + + it('should not throw an error if the category is null', async () => { + await expect( + bulkCreate({ cases: getCases({ category: null }) }, clientArgs, casesClientMock) + ).resolves.not.toThrow(); + }); + + it('should throw an error if the category length is too long', async () => { + await expect( + bulkCreate( + { + cases: getCases({ category: 'A very long category with more than fifty characters!' }), + }, + clientArgs, + casesClientMock + ) + ).rejects.toThrow( + 'Failed to bulk create cases: Error: The length of the category is too long.' + ); + }); + + it('should throw an error if the category is an empty string', async () => { + await expect( + bulkCreate({ cases: getCases({ category: '' }) }, clientArgs, casesClientMock) + ).rejects.toThrow( + 'Failed to bulk create cases: Error: The category field cannot be an empty string.,Invalid value "" supplied to "cases,category"' + ); + }); + + it('should throw an error if the category is a string with empty characters', async () => { + await expect( + bulkCreate({ cases: getCases({ category: ' ' }) }, clientArgs, casesClientMock) + ).rejects.toThrow( + 'Failed to bulk create cases: Error: The category field cannot be an empty string.,Invalid value " " supplied to "cases,category"' + ); + }); + + it('should trim category', async () => { + await bulkCreate( + { cases: getCases({ category: 'reporting ' }) }, + clientArgs, + casesClientMock + ); + + const category = + clientArgs.services.caseService.bulkCreateCases.mock.calls[0][0].cases[0].category; + + expect(category).toEqual('reporting'); + }); + }); + + 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([ + { + owner: theCase.owner, + customFields: [ + { + key: 'first_key', + type: CustomFieldTypes.TEXT, + label: 'foo', + required: true, + }, + { + key: 'second_key', + type: CustomFieldTypes.TOGGLE, + label: 'foo', + required: false, + }, + ], + }, + ]); + + const theCustomFields: CaseCustomFields = [ + { + key: 'first_key', + type: CustomFieldTypes.TEXT, + value: 'this is a text field value', + }, + { + key: 'second_key', + type: CustomFieldTypes.TOGGLE, + value: true, + }, + ]; + + it('should bulkCreate customFields correctly', async () => { + await expect( + bulkCreate({ cases: getCases({ customFields: theCustomFields }) }, clientArgs, casesClient) + ).resolves.not.toThrow(); + + const customFields = + clientArgs.services.caseService.bulkCreateCases.mock.calls[0][0].cases[0].customFields; + + expect(customFields).toEqual(theCustomFields); + }); + + 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, + }, + ], + }, + ]); + + await expect( + bulkCreate({ cases: getCases() }, clientArgs, casesClient) + ).resolves.not.toThrow(); + + const customFields = + 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, + }, + ], + }, + ]); + + 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 () => { + 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: 'missing field 2', + required: true, + }, + ], + }, + ]); + + await expect( + bulkCreate( + { + cases: getCases({ + customFields: [ + { + key: 'first_key', + type: CustomFieldTypes.TEXT, + value: null, + }, + { + key: 'second_key', + type: CustomFieldTypes.TOGGLE, + value: null, + }, + ], + }), + }, + clientArgs, + casesClient + ) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Failed to bulk create cases: Error: Missing required custom fields: \\"missing field 1\\", \\"missing field 2\\""` + ); + }); + + it('throws error when the customFields array is too long', async () => { + await expect( + bulkCreate( + { + cases: getCases({ + customFields: Array(MAX_CUSTOM_FIELDS_PER_CASE + 1).fill(theCustomFields[0]), + }), + }, + clientArgs, + casesClient + ) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Failed to bulk create cases: Error: The length of the field customFields is too long. Array must be of length <= 10."` + ); + }); + + it('throws with duplicated customFields keys', async () => { + await expect( + bulkCreate( + { + cases: getCases({ + customFields: [ + { + key: 'duplicated_key', + type: CustomFieldTypes.TEXT, + value: 'this is a text field value', + }, + { + key: 'duplicated_key', + type: CustomFieldTypes.TOGGLE, + value: true, + }, + ], + }), + }, + clientArgs, + casesClient + ) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Failed to bulk create cases: Error: Invalid duplicated custom field keys in request: duplicated_key"` + ); + }); + + it('throws error when customFields keys are not present in configuration', async () => { + await expect( + bulkCreate( + { + cases: getCases({ + customFields: [ + { + key: 'missing_key', + type: CustomFieldTypes.TEXT, + value: null, + }, + ], + }), + }, + clientArgs, + casesClient + ) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Failed to bulk create cases: Error: Invalid custom field keys: missing_key"` + ); + }); + + 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( + { + cases: getCases({ + customFields: [ + { + key: 'first_key', + type: CustomFieldTypes.TOGGLE, + value: true, + }, + { + key: 'second_key', + type: CustomFieldTypes.TEXT, + value: 'foobar', + }, + ], + }), + }, + clientArgs, + casesClient + ) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Failed to bulk create cases: Error: The following custom fields have the wrong type in the request: first_key,second_key"` + ); + }); + + it('should get all configurations', async () => { + await expect( + bulkCreate({ cases: getCases({ customFields: theCustomFields }) }, clientArgs, casesClient) + ).resolves.not.toThrow(); + + expect(casesClient.configure.get).toHaveBeenCalledWith(); + }); + + it('validate required custom fields from different owners', async () => { + const casesWithDifferentOwners = [getCases()[0], getCases({ owner: 'cases' })[0]]; + + casesClient.configure.get = jest.fn().mockResolvedValue([ + { + owner: theCase.owner, + customFields: [ + { + key: 'sec_first_key', + type: CustomFieldTypes.TEXT, + label: 'sec custom field', + required: false, + }, + ], + }, + { + owner: 'cases', + customFields: [ + { + key: 'cases_first_key', + type: CustomFieldTypes.TEXT, + label: 'stack cases custom field', + required: true, + }, + ], + }, + ]); + + await expect( + bulkCreate({ cases: casesWithDifferentOwners }, clientArgs, casesClient) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Failed to bulk create cases: Error: Missing required custom fields: \\"stack cases custom field\\""` + ); + }); + + it('should fill out missing custom fields from different owners correctly', async () => { + const casesWithDifferentOwners = [getCases()[0], getCases({ owner: 'cases' })[0]]; + + casesClient.configure.get = jest.fn().mockResolvedValue([ + { + owner: theCase.owner, + customFields: [ + { + key: 'sec_first_key', + type: CustomFieldTypes.TEXT, + label: 'sec custom field', + required: false, + }, + ], + }, + { + owner: 'cases', + customFields: [ + { + key: 'cases_first_key', + type: CustomFieldTypes.TEXT, + label: 'stack cases custom field', + required: false, + }, + ], + }, + ]); + + await bulkCreate({ cases: casesWithDifferentOwners }, clientArgs, casesClient); + + const cases = clientArgs.services.caseService.bulkCreateCases.mock.calls[0][0].cases; + + expect(cases[0].owner).toBe('securitySolution'); + expect(cases[1].owner).toBe('cases'); + + expect(cases[0].customFields).toEqual([{ key: 'sec_first_key', type: 'text', value: null }]); + expect(cases[1].customFields).toEqual([ + { key: 'cases_first_key', type: 'text', value: null }, + ]); + }); + }); + + describe('User actions', () => { + const theCase = getCases()[0]; + + const caseWithOnlyRequiredFields = omit(theCase, [ + 'assignees', + 'category', + 'severity', + 'customFields', + ]) as CasePostRequest; + + const caseWithOptionalFields: CasePostRequest = { + ...theCase, + category: 'My category', + severity: CaseSeverity.CRITICAL, + customFields: [ + { + key: 'first_customField_key', + type: CustomFieldTypes.TEXT, + value: 'this is a text field value', + }, + { + key: 'second_customField_key', + type: CustomFieldTypes.TOGGLE, + value: true, + }, + ], + }; + + const casesClient = createCasesClientMock(); + const clientArgs = createCasesClientMockArgs(); + clientArgs.services.caseService.bulkCreateCases.mockResolvedValue({ saved_objects: [caseSO] }); + + casesClient.configure.get = jest.fn().mockResolvedValue([ + { + owner: caseWithOptionalFields.owner, + customFields: [ + { + key: 'first_customField_key', + type: CustomFieldTypes.TEXT, + label: 'foo', + required: false, + }, + { + key: 'second_customField_key', + type: CustomFieldTypes.TOGGLE, + label: 'foo', + required: false, + }, + ], + }, + ]); + + it('should bulkCreate a user action with defaults correctly', async () => { + await bulkCreate({ cases: [caseWithOnlyRequiredFields] }, clientArgs, casesClient); + + expect( + clientArgs.services.userActionService.creator.bulkCreateUserAction + ).toHaveBeenCalledWith({ + userActions: [ + { + caseId: 'mock-id-1', + owner: 'securitySolution', + payload: { + assignees: [], + category: null, + connector: { fields: null, id: 'none', name: 'none', type: '.none' }, + customFields: [], + description: 'This is a brand new case of a bad meanie defacing data', + owner: 'securitySolution', + settings: { syncAlerts: true }, + severity: 'low', + tags: ['defacement'], + title: 'Super Bad Security Issue', + }, + type: 'create_case', + user: { + email: 'damaged_raccoon@elastic.co', + full_name: 'Damaged Raccoon', + profile_uid: 'u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0', + username: 'damaged_raccoon', + }, + }, + ], + }); + }); + + it('should bulkCreate a user action with optional fields set correctly', async () => { + await bulkCreate({ cases: [caseWithOptionalFields] }, clientArgs, casesClient); + + expect( + clientArgs.services.userActionService.creator.bulkCreateUserAction + ).toHaveBeenCalledWith({ + userActions: [ + { + caseId: 'mock-id-1', + owner: 'securitySolution', + payload: { + assignees: [], + category: null, + connector: { fields: null, id: 'none', name: 'none', type: '.none' }, + customFields: [], + description: 'This is a brand new case of a bad meanie defacing data', + owner: 'securitySolution', + settings: { syncAlerts: true }, + severity: 'low', + tags: ['defacement'], + title: 'Super Bad Security Issue', + }, + type: 'create_case', + user: { + email: 'damaged_raccoon@elastic.co', + full_name: 'Damaged Raccoon', + profile_uid: 'u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0', + username: 'damaged_raccoon', + }, + }, + ], + }); + }); + }); +}); diff --git a/x-pack/plugins/cases/server/client/cases/bulk_create.ts b/x-pack/plugins/cases/server/client/cases/bulk_create.ts new file mode 100644 index 00000000000000..fea7986a9169d6 --- /dev/null +++ b/x-pack/plugins/cases/server/client/cases/bulk_create.ts @@ -0,0 +1,228 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import Boom from '@hapi/boom'; +import { partition } from 'lodash'; + +import type { SavedObject } from '@kbn/core/server'; +import { SavedObjectsUtils } from '@kbn/core/server'; + +import type { Case, CustomFieldsConfiguration, User } from '../../../common/types/domain'; +import { CaseSeverity, UserActionTypes } from '../../../common/types/domain'; +import { decodeWithExcessOrThrow } from '../../../common/api'; + +import { Operations } from '../../authorization'; +import { createCaseError } from '../../common/error'; +import { flattenCaseSavedObject, isSOError, transformNewCase } from '../../common/utils'; +import type { CasesClient, CasesClientArgs } from '..'; +import { LICENSING_CASE_ASSIGNMENT_FEATURE } from '../../common/constants'; +import { decodeOrThrow } from '../../../common/api/runtime_types'; +import type { + BulkCreateCasesRequest, + BulkCreateCasesResponse, + CasePostRequest, +} from '../../../common/types/api'; +import { BulkCreateCasesResponseRt, BulkCreateCasesRequestRt } from '../../../common/types/api'; +import { validateCustomFields } from './validators'; +import { normalizeCreateCaseRequest } from './utils'; +import type { BulkCreateCasesArgs } from '../../services/cases/types'; +import type { NotifyAssigneesArgs } from '../../services/notifications/types'; +import type { CaseTransformedAttributes } from '../../common/types/case'; + +export const bulkCreate = async ( + data: BulkCreateCasesRequest, + clientArgs: CasesClientArgs, + casesClient: CasesClient +): Promise => { + const { + services: { caseService, userActionService, licensingService, notificationService }, + user, + logger, + authorization: auth, + } = clientArgs; + + try { + const decodedData = decodeWithExcessOrThrow(BulkCreateCasesRequestRt)(data); + const configurations = await casesClient.configure.get(); + + const customFieldsConfigurationMap: Map = new Map( + configurations.map((conf) => [conf.owner, conf.customFields]) + ); + + const casesWithIds = getCaseWithIds(decodedData); + + await auth.ensureAuthorized({ + operation: Operations.createCase, + entities: casesWithIds.map((theCase) => ({ owner: theCase.owner, id: theCase.id })), + }); + + const hasPlatinumLicenseOrGreater = await licensingService.isAtLeastPlatinum(); + + const bulkCreateRequest: BulkCreateCasesArgs['cases'] = []; + + for (const theCase of casesWithIds) { + const customFieldsConfiguration = customFieldsConfigurationMap.get(theCase.owner); + + validateRequest({ theCase, customFieldsConfiguration, hasPlatinumLicenseOrGreater }); + + bulkCreateRequest.push( + createBulkCreateCaseRequest({ theCase, user, customFieldsConfiguration }) + ); + } + + const bulkCreateResponse = await caseService.bulkCreateCases({ + cases: bulkCreateRequest, + refresh: false, + }); + + const userActions = []; + const assigneesPerCase: NotifyAssigneesArgs[] = []; + const res: Case[] = []; + + const [errors, casesSOs] = partition(bulkCreateResponse.saved_objects, isSOError); + + if (errors.length > 0) { + const firstError = errors[0]; + throw new Boom.Boom(firstError.error.error, { + statusCode: firstError.error.statusCode, + message: firstError.error.message, + }); + } + + for (const theCase of casesSOs) { + userActions.push(createBulkCreateUserActionsRequest({ theCase, user })); + + if (theCase.attributes.assignees && theCase.attributes.assignees.length !== 0) { + const assigneesWithoutCurrentUser = theCase.attributes.assignees.filter( + (assignee) => assignee.uid !== user.profile_uid + ); + + assigneesPerCase.push({ assignees: assigneesWithoutCurrentUser, theCase }); + } + + res.push( + flattenCaseSavedObject({ + savedObject: theCase, + }) + ); + } + + await userActionService.creator.bulkCreateUserAction({ userActions }); + + if (assigneesPerCase.length > 0) { + licensingService.notifyUsage(LICENSING_CASE_ASSIGNMENT_FEATURE); + await notificationService.bulkNotifyAssignees(assigneesPerCase); + } + + return decodeOrThrow(BulkCreateCasesResponseRt)({ cases: res }); + } catch (error) { + throw createCaseError({ message: `Failed to bulk create cases: ${error}`, error, logger }); + } +}; + +const getCaseWithIds = ( + req: BulkCreateCasesRequest +): Array<{ id: string } & BulkCreateCasesRequest['cases'][number]> => + req.cases.map((theCase) => ({ + ...theCase, + id: theCase.id ?? SavedObjectsUtils.generateId(), + })); + +const validateRequest = ({ + theCase, + customFieldsConfiguration, + hasPlatinumLicenseOrGreater, +}: { + theCase: BulkCreateCasesRequest['cases'][number]; + customFieldsConfiguration?: CustomFieldsConfiguration; + hasPlatinumLicenseOrGreater: boolean; +}) => { + const customFieldsValidationParams = { + requestCustomFields: theCase.customFields, + customFieldsConfiguration, + }; + + validateCustomFields(customFieldsValidationParams); + validateAssigneesUsage({ assignees: theCase.assignees, hasPlatinumLicenseOrGreater }); +}; + +const validateAssigneesUsage = ({ + assignees, + hasPlatinumLicenseOrGreater, +}: { + assignees?: BulkCreateCasesRequest['cases'][number]['assignees']; + hasPlatinumLicenseOrGreater: boolean; +}) => { + /** + * Assign users to a case is only available to Platinum+ + */ + + if (assignees && assignees.length !== 0) { + if (!hasPlatinumLicenseOrGreater) { + throw Boom.forbidden( + 'In order to assign users to cases, you must be subscribed to an Elastic Platinum license' + ); + } + } +}; + +const createBulkCreateCaseRequest = ({ + theCase, + customFieldsConfiguration, + user, +}: { + theCase: { id: string } & BulkCreateCasesRequest['cases'][number]; + customFieldsConfiguration?: CustomFieldsConfiguration; + user: User; +}): BulkCreateCasesArgs['cases'][number] => { + const { id, ...caseWithoutId } = theCase; + + /** + * Trim title, category, description and tags + * and fill out missing custom fields + * before saving to ES + */ + + const normalizedCase = normalizeCreateCaseRequest(caseWithoutId, customFieldsConfiguration); + + return { + id, + ...transformNewCase({ + user, + newCase: normalizedCase, + }), + }; +}; + +const createBulkCreateUserActionsRequest = ({ + theCase, + user, +}: { + theCase: SavedObject; + user: User; +}) => { + const userActionPayload: CasePostRequest = { + title: theCase.attributes.title, + tags: theCase.attributes.tags, + connector: theCase.attributes.connector, + settings: theCase.attributes.settings, + owner: theCase.attributes.owner, + description: theCase.attributes.description, + severity: theCase.attributes.severity ?? CaseSeverity.LOW, + assignees: theCase.attributes.assignees ?? [], + category: theCase.attributes.category ?? null, + customFields: theCase.attributes.customFields ?? [], + }; + + return { + type: UserActionTypes.create_case, + caseId: theCase.id, + user, + payload: userActionPayload, + owner: theCase.attributes.owner, + }; +}; diff --git a/x-pack/plugins/cases/server/client/cases/client.ts b/x-pack/plugins/cases/server/client/cases/client.ts index bbe91b1ad791ed..4a3ed9e6c4b055 100644 --- a/x-pack/plugins/cases/server/client/cases/client.ts +++ b/x-pack/plugins/cases/server/client/cases/client.ts @@ -18,6 +18,8 @@ import type { AllReportersFindRequest, GetRelatedCasesByAlertResponse, CasesBulkGetResponse, + BulkCreateCasesRequest, + BulkCreateCasesResponse, } from '../../../common/types/api'; import type { CasesClient } from '../client'; import type { CasesClientInternal } from '../client_internal'; @@ -31,6 +33,7 @@ import { get, resolve, getCasesByAlertID, getReporters, getTags, getCategories } import type { PushParams } from './push'; import { push } from './push'; import { update } from './update'; +import { bulkCreate } from './bulk_create'; /** * API for interacting with the cases entities. @@ -40,6 +43,10 @@ export interface CasesSubClient { * Creates a case. */ create(data: CasePostRequest): Promise; + /** + * Bulk create cases. + */ + bulkCreate(data: BulkCreateCasesRequest): Promise; /** * Returns cases that match the search criteria. * @@ -103,6 +110,7 @@ export const createCasesSubClient = ( ): CasesSubClient => { const casesSubClient: CasesSubClient = { create: (data: CasePostRequest) => create(data, clientArgs, casesClient), + bulkCreate: (data: BulkCreateCasesRequest) => bulkCreate(data, clientArgs, casesClient), find: (params: CasesFindRequest) => find(params, clientArgs), get: (params: GetParams) => get(params, clientArgs), resolve: (params: GetParams) => resolve(params, clientArgs), 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 b7cc876c476558..b65061d403fec2 100644 --- a/x-pack/plugins/cases/server/client/cases/create.test.ts +++ b/x-pack/plugins/cases/server/client/cases/create.test.ts @@ -51,7 +51,7 @@ describe('create', () => { describe('Assignees', () => { const clientArgs = createCasesClientMockArgs(); - clientArgs.services.caseService.postNewCase.mockResolvedValue(caseSO); + clientArgs.services.caseService.createCase.mockResolvedValue(caseSO); beforeEach(() => { jest.clearAllMocks(); @@ -108,11 +108,19 @@ describe('create', () => { `Failed to create case: Error: The length of the field assignees is too long. Array must be of length <= ${MAX_ASSIGNEES_PER_CASE}.` ); }); + + it('should throw if the user does not have the correct license', async () => { + clientArgs.services.licensingService.isAtLeastPlatinum.mockResolvedValue(false); + + await expect(create(theCase, clientArgs, casesClientMock)).rejects.toThrow( + `Failed to create case: Error: In order to assign users to cases, you must be subscribed to an Elastic Platinum license` + ); + }); }); describe('Attributes', () => { const clientArgs = createCasesClientMockArgs(); - clientArgs.services.caseService.postNewCase.mockResolvedValue(caseSO); + clientArgs.services.caseService.createCase.mockResolvedValue(caseSO); beforeEach(() => { jest.clearAllMocks(); @@ -130,7 +138,7 @@ describe('create', () => { describe('title', () => { const clientArgs = createCasesClientMockArgs(); - clientArgs.services.caseService.postNewCase.mockResolvedValue(caseSO); + clientArgs.services.caseService.createCase.mockResolvedValue(caseSO); beforeEach(() => { jest.clearAllMocks(); @@ -173,7 +181,7 @@ describe('create', () => { it('should trim title', async () => { await create({ ...theCase, title: 'title with spaces ' }, clientArgs, casesClientMock); - expect(clientArgs.services.caseService.postNewCase).toHaveBeenCalledWith( + expect(clientArgs.services.caseService.createCase).toHaveBeenCalledWith( expect.objectContaining({ attributes: { ...theCase, @@ -199,7 +207,7 @@ describe('create', () => { describe('description', () => { const clientArgs = createCasesClientMockArgs(); - clientArgs.services.caseService.postNewCase.mockResolvedValue(caseSO); + clientArgs.services.caseService.createCase.mockResolvedValue(caseSO); beforeEach(() => { jest.clearAllMocks(); @@ -250,7 +258,7 @@ describe('create', () => { casesClientMock ); - expect(clientArgs.services.caseService.postNewCase).toHaveBeenCalledWith( + expect(clientArgs.services.caseService.createCase).toHaveBeenCalledWith( expect.objectContaining({ attributes: { ...theCase, @@ -276,7 +284,7 @@ describe('create', () => { describe('tags', () => { const clientArgs = createCasesClientMockArgs(); - clientArgs.services.caseService.postNewCase.mockResolvedValue(caseSO); + clientArgs.services.caseService.createCase.mockResolvedValue(caseSO); beforeEach(() => { jest.clearAllMocks(); @@ -329,7 +337,7 @@ describe('create', () => { it('should trim tags', async () => { await create({ ...theCase, tags: ['pepsi ', 'coke'] }, clientArgs, casesClientMock); - expect(clientArgs.services.caseService.postNewCase).toHaveBeenCalledWith( + expect(clientArgs.services.caseService.createCase).toHaveBeenCalledWith( expect.objectContaining({ attributes: { ...theCase, @@ -355,7 +363,7 @@ describe('create', () => { describe('Category', () => { const clientArgs = createCasesClientMockArgs(); - clientArgs.services.caseService.postNewCase.mockResolvedValue(caseSO); + clientArgs.services.caseService.createCase.mockResolvedValue(caseSO); beforeEach(() => { jest.clearAllMocks(); @@ -396,7 +404,7 @@ describe('create', () => { it('should trim category', async () => { await create({ ...theCase, category: 'reporting ' }, clientArgs, casesClientMock); - expect(clientArgs.services.caseService.postNewCase).toHaveBeenCalledWith( + expect(clientArgs.services.caseService.createCase).toHaveBeenCalledWith( expect.objectContaining({ attributes: { ...theCase, @@ -421,7 +429,7 @@ describe('create', () => { describe('Custom Fields', () => { const clientArgs = createCasesClientMockArgs(); - clientArgs.services.caseService.postNewCase.mockResolvedValue(caseSO); + clientArgs.services.caseService.createCase.mockResolvedValue(caseSO); const casesClient = createCasesClientMock(); casesClient.configure.get = jest.fn().mockResolvedValue([ @@ -473,7 +481,7 @@ describe('create', () => { ) ).resolves.not.toThrow(); - expect(clientArgs.services.caseService.postNewCase).toHaveBeenCalledWith( + expect(clientArgs.services.caseService.createCase).toHaveBeenCalledWith( expect.objectContaining({ attributes: { ...theCase, @@ -517,7 +525,7 @@ describe('create', () => { ]); await expect(create({ ...theCase }, clientArgs, casesClient)).resolves.not.toThrow(); - expect(clientArgs.services.caseService.postNewCase).toHaveBeenCalledWith( + expect(clientArgs.services.caseService.createCase).toHaveBeenCalledWith( expect.objectContaining({ attributes: { ...theCase, @@ -758,7 +766,7 @@ describe('create', () => { const casesClient = createCasesClientMock(); const clientArgs = createCasesClientMockArgs(); - clientArgs.services.caseService.postNewCase.mockResolvedValue(caseSO); + clientArgs.services.caseService.createCase.mockResolvedValue(caseSO); casesClient.configure.get = jest.fn().mockResolvedValue([ { @@ -784,26 +792,28 @@ describe('create', () => { await create(caseWithOnlyRequiredFields, clientArgs, casesClient); expect(clientArgs.services.userActionService.creator.createUserAction).toHaveBeenCalledWith({ - caseId: 'mock-id-1', - owner: 'securitySolution', - payload: { - assignees: [], - category: null, - connector: { fields: null, id: '.none', name: 'None', type: '.none' }, - customFields: [], - description: 'testing sir', + userAction: { + caseId: 'mock-id-1', owner: 'securitySolution', - settings: { syncAlerts: true }, - severity: 'low', - tags: [], - title: 'My Case', - }, - type: 'create_case', - user: { - email: 'damaged_raccoon@elastic.co', - full_name: 'Damaged Raccoon', - profile_uid: 'u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0', - username: 'damaged_raccoon', + payload: { + assignees: [], + category: null, + connector: { fields: null, id: '.none', name: 'None', type: '.none' }, + customFields: [], + description: 'testing sir', + owner: 'securitySolution', + settings: { syncAlerts: true }, + severity: 'low', + tags: [], + title: 'My Case', + }, + type: 'create_case', + user: { + email: 'damaged_raccoon@elastic.co', + full_name: 'Damaged Raccoon', + profile_uid: 'u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0', + username: 'damaged_raccoon', + }, }, }); }); @@ -812,26 +822,28 @@ describe('create', () => { await create(caseWithOptionalFields, clientArgs, casesClient); expect(clientArgs.services.userActionService.creator.createUserAction).toHaveBeenCalledWith({ - caseId: 'mock-id-1', - owner: 'securitySolution', - payload: { - assignees: [{ uid: '1' }], - category: 'My category', - connector: { fields: null, id: '.none', name: 'None', type: '.none' }, - customFields: caseWithOptionalFields.customFields, - description: 'testing sir', + userAction: { + caseId: 'mock-id-1', owner: 'securitySolution', - settings: { syncAlerts: true }, - severity: 'critical', - tags: [], - title: 'My Case', - }, - type: 'create_case', - user: { - email: 'damaged_raccoon@elastic.co', - full_name: 'Damaged Raccoon', - profile_uid: 'u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0', - username: 'damaged_raccoon', + payload: { + assignees: [{ uid: '1' }], + category: 'My category', + connector: { fields: null, id: '.none', name: 'None', type: '.none' }, + customFields: caseWithOptionalFields.customFields, + description: 'testing sir', + owner: 'securitySolution', + settings: { syncAlerts: true }, + severity: 'critical', + tags: [], + title: 'My Case', + }, + type: 'create_case', + user: { + email: 'damaged_raccoon@elastic.co', + full_name: 'Damaged Raccoon', + profile_uid: 'u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0', + username: 'damaged_raccoon', + }, }, }); }); diff --git a/x-pack/plugins/cases/server/client/cases/create.ts b/x-pack/plugins/cases/server/client/cases/create.ts index d48eaa080dee8c..4e548d07a2ef6d 100644 --- a/x-pack/plugins/cases/server/client/cases/create.ts +++ b/x-pack/plugins/cases/server/client/cases/create.ts @@ -23,7 +23,7 @@ import type { CasePostRequest } from '../../../common/types/api'; import { CasePostRequestRt } from '../../../common/types/api'; import {} from '../utils'; import { validateCustomFields } from './validators'; -import { fillMissingCustomFields } from './utils'; +import { normalizeCreateCaseRequest } from './utils'; /** * Creates a new case. @@ -82,39 +82,31 @@ export const create = async ( * before saving to ES */ - const normalizedQuery = { - ...query, - title: query.title.trim(), - description: query.description.trim(), - category: query.category?.trim() ?? null, - tags: query.tags?.map((tag) => tag.trim()) ?? [], - customFields: fillMissingCustomFields({ - customFields: query.customFields, - customFieldsConfiguration, - }), - }; + const normalizedCase = normalizeCreateCaseRequest(query, customFieldsConfiguration); - const newCase = await caseService.postNewCase({ + const newCase = await caseService.createCase({ attributes: transformNewCase({ user, - newCase: normalizedQuery, + newCase: normalizedCase, }), id: savedObjectID, refresh: false, }); await userActionService.creator.createUserAction({ - type: UserActionTypes.create_case, - caseId: newCase.id, - user, - payload: { - ...query, - severity: query.severity ?? CaseSeverity.LOW, - assignees: query.assignees ?? [], - category: query.category ?? null, - customFields: query.customFields ?? [], + userAction: { + type: UserActionTypes.create_case, + caseId: newCase.id, + user, + payload: { + ...query, + severity: query.severity ?? CaseSeverity.LOW, + assignees: query.assignees ?? [], + category: query.category ?? null, + customFields: query.customFields ?? [], + }, + owner: newCase.attributes.owner, }, - owner: newCase.attributes.owner, }); if (query.assignees && query.assignees.length !== 0) { diff --git a/x-pack/plugins/cases/server/client/cases/push.ts b/x-pack/plugins/cases/server/client/cases/push.ts index cf3732c065e23a..fba7908ed2762d 100644 --- a/x-pack/plugins/cases/server/client/cases/push.ts +++ b/x-pack/plugins/cases/server/client/cases/push.ts @@ -261,11 +261,13 @@ export const push = async ( if (shouldMarkAsClosed) { await userActionService.creator.createUserAction({ - type: UserActionTypes.status, - payload: { status: CaseStatuses.closed }, - user, - caseId, - owner: myCase.attributes.owner, + userAction: { + type: UserActionTypes.status, + payload: { status: CaseStatuses.closed }, + user, + caseId, + owner: myCase.attributes.owner, + }, refresh: false, }); @@ -275,11 +277,13 @@ export const push = async ( } await userActionService.creator.createUserAction({ - type: UserActionTypes.pushed, - payload: { externalService }, - user, - caseId, - owner: myCase.attributes.owner, + userAction: { + type: UserActionTypes.pushed, + payload: { externalService }, + user, + caseId, + owner: myCase.attributes.owner, + }, }); /* End of update case with push information */ diff --git a/x-pack/plugins/cases/server/client/cases/update.ts b/x-pack/plugins/cases/server/client/cases/update.ts index b2baff721302fc..1f7a489127a087 100644 --- a/x-pack/plugins/cases/server/client/cases/update.ts +++ b/x-pack/plugins/cases/server/client/cases/update.ts @@ -369,7 +369,7 @@ export const update = async ( ); } - const configurations = await casesClient.configure.get({}); + const configurations = await casesClient.configure.get(); const customFieldsConfigurationMap: Map = new Map( configurations.map((conf) => [conf.owner, conf.customFields]) ); 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 a1017b20b4286f..2d697b597baacf 100644 --- a/x-pack/plugins/cases/server/client/cases/utils.test.ts +++ b/x-pack/plugins/cases/server/client/cases/utils.test.ts @@ -5,6 +5,8 @@ * 2.0. */ +import { omit } from 'lodash'; + import { comment as commentObj, userActions, @@ -28,9 +30,16 @@ import { formatComments, addKibanaInformationToDescription, fillMissingCustomFields, + normalizeCreateCaseRequest, } from './utils'; import type { CaseCustomFields } from '../../../common/types/domain'; -import { CaseStatuses, CustomFieldTypes, UserActionActions } from '../../../common/types/domain'; +import { + CaseStatuses, + CustomFieldTypes, + UserActionActions, + CaseSeverity, + ConnectorTypes, +} from '../../../common/types/domain'; import { flattenCaseSavedObject } from '../../common/utils'; import { SECURITY_SOLUTION_OWNER } from '../../../common/constants'; import { casesConnectors } from '../../connectors'; @@ -1471,3 +1480,96 @@ describe('utils', () => { }); }); }); + +describe('normalizeCreateCaseRequest', () => { + const theCase = { + title: 'My Case', + tags: [], + description: 'testing sir', + connector: { + id: '.none', + name: 'None', + type: ConnectorTypes.none, + fields: null, + }, + settings: { syncAlerts: true }, + severity: CaseSeverity.LOW, + owner: SECURITY_SOLUTION_OWNER, + assignees: [{ uid: '1' }], + category: 'my category', + customFields: [], + }; + + it('should trim title', async () => { + expect(normalizeCreateCaseRequest({ ...theCase, title: 'title with spaces ' })).toEqual({ + ...theCase, + title: 'title with spaces', + }); + }); + + it('should trim description', async () => { + expect( + normalizeCreateCaseRequest({ + ...theCase, + description: 'this is a description with spaces!! ', + }) + ).toEqual({ + ...theCase, + description: 'this is a description with spaces!!', + }); + }); + + it('should trim tags', async () => { + expect( + normalizeCreateCaseRequest({ + ...theCase, + tags: ['pepsi ', 'coke'], + }) + ).toEqual({ + ...theCase, + tags: ['pepsi', 'coke'], + }); + }); + + it('should trim category', async () => { + expect( + normalizeCreateCaseRequest({ + ...theCase, + category: 'reporting ', + }) + ).toEqual({ + ...theCase, + category: 'reporting', + }); + }); + + it('should set the category to null if missing', async () => { + expect(normalizeCreateCaseRequest(omit(theCase, 'category'))).toEqual({ + ...theCase, + category: null, + }); + }); + + it('should fill out missing custom fields', async () => { + expect( + normalizeCreateCaseRequest(omit(theCase, 'customFields'), [ + { + key: 'first_key', + type: CustomFieldTypes.TEXT, + label: 'foo', + required: false, + }, + ]) + ).toEqual({ + ...theCase, + customFields: [{ key: 'first_key', type: CustomFieldTypes.TEXT, value: null }], + }); + }); + + it('should set the customFields to an empty array if missing', async () => { + expect(normalizeCreateCaseRequest(omit(theCase, 'customFields'))).toEqual({ + ...theCase, + customFields: [], + }); + }); +}); diff --git a/x-pack/plugins/cases/server/client/cases/utils.ts b/x-pack/plugins/cases/server/client/cases/utils.ts index 9e7f1ab73af206..474871af02c5dc 100644 --- a/x-pack/plugins/cases/server/client/cases/utils.ts +++ b/x-pack/plugins/cases/server/client/cases/utils.ts @@ -25,6 +25,7 @@ import type { } from '../../../common/types/domain'; import { CaseStatuses, UserActionTypes, AttachmentType } from '../../../common/types/domain'; import type { + CasePostRequest, CaseRequestCustomFields, CaseUserActionsDeprecatedResponse, } from '../../../common/types/api'; @@ -480,3 +481,18 @@ export const fillMissingCustomFields = ({ return [...customFields, ...missingCustomFields]; }; + +export const normalizeCreateCaseRequest = ( + request: CasePostRequest, + customFieldsConfiguration?: CustomFieldsConfiguration +) => ({ + ...request, + title: request.title.trim(), + description: request.description.trim(), + category: request.category?.trim() ?? null, + tags: request.tags?.map((tag) => tag.trim()) ?? [], + customFields: fillMissingCustomFields({ + customFields: request.customFields, + customFieldsConfiguration, + }), +}); diff --git a/x-pack/plugins/cases/server/client/configure/client.ts b/x-pack/plugins/cases/server/client/configure/client.ts index 1482ea501ca87d..1261f1061a3717 100644 --- a/x-pack/plugins/cases/server/client/configure/client.ts +++ b/x-pack/plugins/cases/server/client/configure/client.ts @@ -69,7 +69,7 @@ export interface ConfigureSubClient { /** * Retrieves the external connector configuration for a particular case owner. */ - get(params: GetConfigurationFindRequest): Promise; + get(params?: GetConfigurationFindRequest): Promise; /** * Retrieves the valid external connectors supported by the cases plugin. */ @@ -120,7 +120,7 @@ export const createConfigurationSubClient = ( casesInternalClient: CasesClientInternal ): ConfigureSubClient => { return Object.freeze({ - get: (params: GetConfigurationFindRequest) => get(params, clientArgs, casesInternalClient), + get: (params?: GetConfigurationFindRequest) => get(params, clientArgs, casesInternalClient), getConnectors: () => getConnectors(clientArgs), update: (configurationId: string, configuration: ConfigurationPatchRequest) => update(configurationId, configuration, clientArgs, casesInternalClient), @@ -130,7 +130,7 @@ export const createConfigurationSubClient = ( }; export async function get( - params: GetConfigurationFindRequest, + params: GetConfigurationFindRequest = {}, clientArgs: CasesClientArgs, casesClientInternal: CasesClientInternal ): Promise { diff --git a/x-pack/plugins/cases/server/client/mocks.ts b/x-pack/plugins/cases/server/client/mocks.ts index 8fd0a61c35f304..7d4015d15a085d 100644 --- a/x-pack/plugins/cases/server/client/mocks.ts +++ b/x-pack/plugins/cases/server/client/mocks.ts @@ -47,6 +47,7 @@ type CasesSubClientMock = jest.Mocked; const createCasesSubClientMock = (): CasesSubClientMock => { return { create: jest.fn(), + bulkCreate: jest.fn(), find: jest.fn(), resolve: jest.fn(), get: jest.fn(), diff --git a/x-pack/plugins/cases/server/common/models/case_with_comments.ts b/x-pack/plugins/cases/server/common/models/case_with_comments.ts index 5e48c38e67d0d8..e1b89d7af791e1 100644 --- a/x-pack/plugins/cases/server/common/models/case_with_comments.ts +++ b/x-pack/plugins/cases/server/common/models/case_with_comments.ts @@ -193,13 +193,15 @@ export class CaseCommentModel { const { id, version, ...queryRestAttributes } = updateRequest; await this.params.services.userActionService.creator.createUserAction({ - type: UserActionTypes.comment, - action: UserActionActions.update, - caseId: this.caseInfo.id, - attachmentId: comment.id, - payload: { attachment: queryRestAttributes }, - user: this.params.user, - owner, + userAction: { + type: UserActionTypes.comment, + action: UserActionActions.update, + caseId: this.caseInfo.id, + attachmentId: comment.id, + payload: { attachment: queryRestAttributes }, + user: this.params.user, + owner, + }, }); } @@ -403,15 +405,17 @@ export class CaseCommentModel { req: AttachmentRequest ) { await this.params.services.userActionService.creator.createUserAction({ - type: UserActionTypes.comment, - action: UserActionActions.create, - caseId: this.caseInfo.id, - attachmentId: comment.id, - payload: { - attachment: req, + userAction: { + type: UserActionTypes.comment, + action: UserActionActions.create, + caseId: this.caseInfo.id, + attachmentId: comment.id, + payload: { + attachment: req, + }, + user: this.params.user, + owner: comment.attributes.owner, }, - user: this.params.user, - owner: comment.attributes.owner, }); } diff --git a/x-pack/plugins/cases/server/common/types.ts b/x-pack/plugins/cases/server/common/types.ts index c79cb96d0d0b66..8855d73e9f4b39 100644 --- a/x-pack/plugins/cases/server/common/types.ts +++ b/x-pack/plugins/cases/server/common/types.ts @@ -52,3 +52,7 @@ export type FileAttachmentRequest = Omit< export type AttachmentSavedObject = SavedObject; export type SOWithErrors = Omit, 'attributes'> & { error: SavedObjectError }; + +export interface SavedObjectsBulkResponseWithErrors { + saved_objects: Array | SOWithErrors>; +} diff --git a/x-pack/plugins/cases/server/services/attachments/index.test.ts b/x-pack/plugins/cases/server/services/attachments/index.test.ts index fc12e26b67ed6e..b089616717e76b 100644 --- a/x-pack/plugins/cases/server/services/attachments/index.test.ts +++ b/x-pack/plugins/cases/server/services/attachments/index.test.ts @@ -21,9 +21,10 @@ import { persistableStateAttachmentAttributes, persistableStateAttachmentAttributesWithoutInjectedId, } from '../../attachment_framework/mocks'; -import { createAlertAttachment, createErrorSO, createUserAttachment } from './test_utils'; +import { createAlertAttachment, createUserAttachment } from './test_utils'; import { AttachmentType } from '../../../common/types/domain'; -import { createSOFindResponse } from '../test_utils'; +import { createErrorSO, createSOFindResponse } from '../test_utils'; +import { CASE_COMMENT_SAVED_OBJECT } from '../../../common'; describe('AttachmentService', () => { const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); @@ -135,9 +136,10 @@ describe('AttachmentService', () => { it('returns error objects unmodified', async () => { const userAttachment = createUserAttachment({ foo: 'bar' }); - const errorResponseObj = createErrorSO(); + const errorResponseObj = createErrorSO(CASE_COMMENT_SAVED_OBJECT); unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({ + // @ts-expect-error: SO client types are wrong saved_objects: [errorResponseObj, userAttachment], }); @@ -411,9 +413,10 @@ describe('AttachmentService', () => { it('returns error objects unmodified', async () => { const userAttachment = createUserAttachment({ foo: 'bar' }); - const errorResponseObj = createErrorSO(); + const errorResponseObj = createErrorSO(CASE_COMMENT_SAVED_OBJECT); unsecuredSavedObjectsClient.bulkUpdate.mockResolvedValue({ + // @ts-expect-error: SO client types are wrong saved_objects: [errorResponseObj, userAttachment], }); diff --git a/x-pack/plugins/cases/server/services/attachments/operations/get.test.ts b/x-pack/plugins/cases/server/services/attachments/operations/get.test.ts index 5ee0cdce357693..590cabcae67a13 100644 --- a/x-pack/plugins/cases/server/services/attachments/operations/get.test.ts +++ b/x-pack/plugins/cases/server/services/attachments/operations/get.test.ts @@ -12,13 +12,9 @@ import type { SavedObjectsFindResponse } from '@kbn/core/server'; import { loggerMock } from '@kbn/logging-mocks'; import { createPersistableStateAttachmentTypeRegistryMock } from '../../../attachment_framework/mocks'; import { AttachmentGetter } from './get'; -import { - createAlertAttachment, - createErrorSO, - createFileAttachment, - createUserAttachment, -} from '../test_utils'; -import { mockPointInTimeFinder, createSOFindResponse } from '../../test_utils'; +import { createAlertAttachment, createFileAttachment, createUserAttachment } from '../test_utils'; +import { mockPointInTimeFinder, createSOFindResponse, createErrorSO } from '../../test_utils'; +import { CASE_COMMENT_SAVED_OBJECT } from '../../../../common'; describe('AttachmentService getter', () => { const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); @@ -50,12 +46,15 @@ describe('AttachmentService getter', () => { it('does not modified the error saved objects', async () => { unsecuredSavedObjectsClient.bulkGet.mockResolvedValue({ - saved_objects: [createUserAttachment(), createErrorSO()], + // @ts-expect-error: SO client types are not correct + saved_objects: [createUserAttachment(), createErrorSO(CASE_COMMENT_SAVED_OBJECT)], }); const res = await attachmentGetter.bulkGet(['1', '2']); - expect(res).toStrictEqual({ saved_objects: [createUserAttachment(), createErrorSO()] }); + expect(res).toStrictEqual({ + saved_objects: [createUserAttachment(), createErrorSO(CASE_COMMENT_SAVED_OBJECT)], + }); }); it('strips excess fields', async () => { diff --git a/x-pack/plugins/cases/server/services/attachments/test_utils.ts b/x-pack/plugins/cases/server/services/attachments/test_utils.ts index ab3c7a6f710be9..b90fac81b54428 100644 --- a/x-pack/plugins/cases/server/services/attachments/test_utils.ts +++ b/x-pack/plugins/cases/server/services/attachments/test_utils.ts @@ -22,19 +22,6 @@ import { } from '../../../common/constants'; import { CASE_REF_NAME, EXTERNAL_REFERENCE_REF_NAME } from '../../common/constants'; -export const createErrorSO = () => - ({ - id: '1', - type: CASE_COMMENT_SAVED_OBJECT, - error: { - error: 'error', - message: 'message', - statusCode: 500, - }, - references: [], - // casting because this complains about attributes not being there - } as unknown as SavedObject); - export const createUserAttachment = ( attributes?: object ): SavedObject => { diff --git a/x-pack/plugins/cases/server/services/cases/index.test.ts b/x-pack/plugins/cases/server/services/cases/index.test.ts index 712e22732b9ff2..db6776b7524747 100644 --- a/x-pack/plugins/cases/server/services/cases/index.test.ts +++ b/x-pack/plugins/cases/server/services/cases/index.test.ts @@ -41,10 +41,15 @@ import { createCaseSavedObjectResponse, basicCaseFields, createSOFindResponse, + createErrorSO, } from '../test_utils'; import { AttachmentService } from '../attachments'; import { PersistableStateAttachmentTypeRegistry } from '../../attachment_framework/persistable_state_registry'; -import type { CaseSavedObjectTransformed, CasePersistedAttributes } from '../../common/types/case'; +import type { + CaseSavedObjectTransformed, + CasePersistedAttributes, + CaseTransformedAttributes, +} from '../../common/types/case'; import { CasePersistedSeverity, CasePersistedStatus, @@ -175,6 +180,101 @@ describe('CasesService', () => { }); }); + describe('execution', () => { + describe('bulkCreateCases', () => { + it('return cases with the SO errors correctly', async () => { + unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({ + // @ts-expect-error: SO client types are not correct + saved_objects: [createCaseSavedObjectResponse(), createErrorSO('cases')], + }); + + const res = await service.bulkCreateCases({ + cases: [ + { + ...createCasePostParams({ + connector: getNoneCaseConnector(), + severity: CaseSeverity.MEDIUM, + }), + id: '1', + }, + ], + }); + + expect(res).toMatchInlineSnapshot(` + Object { + "saved_objects": Array [ + Object { + "attributes": Object { + "assignees": Array [], + "category": null, + "closed_at": null, + "closed_by": null, + "connector": Object { + "fields": null, + "id": "none", + "name": "none", + "type": ".none", + }, + "created_at": "2019-11-25T21:54:48.952Z", + "created_by": Object { + "email": "testemail@elastic.co", + "full_name": "elastic", + "username": "elastic", + }, + "customFields": Array [], + "description": "This is a brand new case of a bad meanie defacing data", + "duration": null, + "external_service": Object { + "connector_id": "none", + "connector_name": ".jira", + "external_id": "100", + "external_title": "awesome", + "external_url": "http://www.google.com", + "pushed_at": "2019-11-25T21:54:48.952Z", + "pushed_by": Object { + "email": "testemail@elastic.co", + "full_name": "elastic", + "username": "elastic", + }, + }, + "owner": "securitySolution", + "settings": Object { + "syncAlerts": true, + }, + "severity": "low", + "status": "open", + "tags": Array [ + "defacement", + ], + "title": "Super Bad Security Issue", + "updated_at": "2019-11-25T21:54:48.952Z", + "updated_by": Object { + "email": "testemail@elastic.co", + "full_name": "elastic", + "username": "elastic", + }, + }, + "id": "1", + "references": Array [], + "type": "cases", + }, + Object { + "error": Object { + "error": "error", + "message": "message", + "statusCode": 500, + }, + "id": "1", + "references": Array [], + "type": "cases", + }, + ], + } + `); + }); + }); + }); + describe('transforms the external model to the Elasticsearch model', () => { describe('patch', () => { it('includes the passed in fields', async () => { @@ -663,11 +763,11 @@ describe('CasesService', () => { }); }); - describe('post', () => { + describe('createCase', () => { it('creates a null external_service field when the attribute was null in the creation parameters', async () => { unsecuredSavedObjectsClient.create.mockResolvedValue(createCaseSavedObjectResponse()); - await service.postNewCase({ + await service.createCase({ attributes: createCasePostParams({ connector: createJiraConnector() }), id: '1', }); @@ -680,7 +780,7 @@ describe('CasesService', () => { it('includes the creation attributes excluding the connector.id and connector_id', async () => { unsecuredSavedObjectsClient.create.mockResolvedValue(createCaseSavedObjectResponse()); - await service.postNewCase({ + await service.createCase({ attributes: createCasePostParams({ connector: createJiraConnector(), externalService: createExternalService(), @@ -780,7 +880,7 @@ describe('CasesService', () => { it('includes default values for total_alerts and total_comments', async () => { unsecuredSavedObjectsClient.create.mockResolvedValue(createCaseSavedObjectResponse()); - await service.postNewCase({ + await service.createCase({ attributes: createCasePostParams({ connector: getNoneCaseConnector(), }), @@ -797,7 +897,7 @@ describe('CasesService', () => { it('moves the connector.id and connector_id to the references', async () => { unsecuredSavedObjectsClient.create.mockResolvedValue(createCaseSavedObjectResponse()); - await service.postNewCase({ + await service.createCase({ attributes: createCasePostParams({ connector: createJiraConnector(), externalService: createExternalService(), @@ -826,7 +926,7 @@ describe('CasesService', () => { it('sets fields to an empty array when it is not included with the connector', async () => { unsecuredSavedObjectsClient.create.mockResolvedValue(createCaseSavedObjectResponse()); - await service.postNewCase({ + await service.createCase({ attributes: createCasePostParams({ connector: createJiraConnector({ setFieldsToNull: true }), externalService: createExternalService(), @@ -842,7 +942,7 @@ describe('CasesService', () => { it('does not create a reference for a none connector', async () => { unsecuredSavedObjectsClient.create.mockResolvedValue(createCaseSavedObjectResponse()); - await service.postNewCase({ + await service.createCase({ attributes: createCasePostParams({ connector: getNoneCaseConnector() }), id: '1', }); @@ -855,7 +955,7 @@ describe('CasesService', () => { it('does not create a reference for an external_service field that is null', async () => { unsecuredSavedObjectsClient.create.mockResolvedValue(createCaseSavedObjectResponse()); - await service.postNewCase({ + await service.createCase({ attributes: createCasePostParams({ connector: getNoneCaseConnector() }), id: '1', }); @@ -875,7 +975,7 @@ describe('CasesService', () => { async (postParamsSeverity, expectedSeverity) => { unsecuredSavedObjectsClient.create.mockResolvedValue(createCaseSavedObjectResponse()); - await service.postNewCase({ + await service.createCase({ attributes: createCasePostParams({ connector: getNoneCaseConnector(), severity: postParamsSeverity, @@ -898,7 +998,7 @@ describe('CasesService', () => { async (postParamsStatus, expectedStatus) => { unsecuredSavedObjectsClient.create.mockResolvedValue(createCaseSavedObjectResponse()); - await service.postNewCase({ + await service.createCase({ attributes: createCasePostParams({ connector: getNoneCaseConnector(), status: postParamsStatus, @@ -912,6 +1012,103 @@ describe('CasesService', () => { } ); }); + + describe('bulkCreateCases', () => { + it('creates a null external_service field when the attribute was null in the creation parameters', async () => { + unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({ + saved_objects: [createCaseSavedObjectResponse()], + }); + + await service.bulkCreateCases({ + cases: [ + { + ...createCasePostParams({ + connector: getNoneCaseConnector(), + severity: CaseSeverity.MEDIUM, + }), + id: '1', + }, + ], + }); + + const bulkCreateRequest = unsecuredSavedObjectsClient.bulkCreate.mock.calls[0]; + + expect(bulkCreateRequest).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "attributes": Object { + "assignees": Array [], + "category": null, + "closed_at": null, + "closed_by": null, + "connector": Object { + "fields": Array [], + "name": "none", + "type": ".none", + }, + "created_at": "2019-11-25T21:54:48.952Z", + "created_by": Object { + "email": "testemail@elastic.co", + "full_name": "elastic", + "username": "elastic", + }, + "customFields": Array [], + "description": "This is a brand new case of a bad meanie defacing data", + "duration": null, + "external_service": null, + "owner": "securitySolution", + "settings": Object { + "syncAlerts": true, + }, + "severity": 10, + "status": 0, + "tags": Array [ + "defacement", + ], + "title": "Super Bad Security Issue", + "total_alerts": -1, + "total_comments": -1, + "updated_at": "2019-11-25T21:54:48.952Z", + "updated_by": Object { + "email": "testemail@elastic.co", + "full_name": "elastic", + "username": "elastic", + }, + }, + "id": "1", + "references": Array [], + "type": "cases", + }, + ], + Object { + "refresh": undefined, + }, + ] + `); + }); + }); + + it('includes default values for total_alerts and total_comments', async () => { + unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({ + saved_objects: [createCaseSavedObjectResponse({})], + }); + + await service.bulkCreateCases({ + cases: [ + { + ...createCasePostParams({ connector: getNoneCaseConnector() }), + id: '1', + }, + ], + }); + + const postAttributes = unsecuredSavedObjectsClient.bulkCreate.mock.calls[0][0][0] + .attributes as CasePersistedAttributes; + + expect(postAttributes.total_alerts).toEqual(-1); + expect(postAttributes.total_comments).toEqual(-1); + }); }); describe('transforms the Elasticsearch model to the external model', () => { @@ -1364,7 +1561,7 @@ describe('CasesService', () => { }); }); - describe('post', () => { + describe('createCase', () => { it('includes the connector.id and connector_id fields in the response', async () => { unsecuredSavedObjectsClient.create.mockResolvedValue( createCaseSavedObjectResponse({ @@ -1373,7 +1570,7 @@ describe('CasesService', () => { }) ); - const res = await service.postNewCase({ + const res = await service.createCase({ attributes: createCasePostParams({ connector: getNoneCaseConnector() }), id: '1', }); @@ -1394,7 +1591,7 @@ describe('CasesService', () => { createCaseSavedObjectResponse({ overrides: { severity: internalSeverityValue } }) ); - const res = await service.postNewCase({ + const res = await service.createCase({ attributes: createCasePostParams({ connector: getNoneCaseConnector() }), id: '1', }); @@ -1414,7 +1611,7 @@ describe('CasesService', () => { createCaseSavedObjectResponse({ overrides: { status: internalStatusValue } }) ); - const res = await service.postNewCase({ + const res = await service.createCase({ attributes: createCasePostParams({ connector: getNoneCaseConnector() }), id: '1', }); @@ -1426,7 +1623,7 @@ describe('CasesService', () => { it('does not include total_alerts and total_comments fields in the response', async () => { unsecuredSavedObjectsClient.create.mockResolvedValue(createCaseSavedObjectResponse({})); - const res = await service.postNewCase({ + const res = await service.createCase({ attributes: createCasePostParams({ connector: getNoneCaseConnector() }), id: '1', }); @@ -1436,6 +1633,109 @@ describe('CasesService', () => { }); }); + describe('bulkCreateCases', () => { + it('includes the connector.id and connector_id fields in the response', async () => { + unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({ + saved_objects: [ + createCaseSavedObjectResponse({ + overrides: { severity: CasePersistedSeverity.MEDIUM }, + }), + ], + }); + + const res = await service.bulkCreateCases({ + cases: [ + { + ...createCasePostParams({ connector: getNoneCaseConnector() }), + id: '1', + }, + ], + }); + + expect(res).toMatchInlineSnapshot(` + Object { + "saved_objects": Array [ + Object { + "attributes": Object { + "assignees": Array [], + "category": null, + "closed_at": null, + "closed_by": null, + "connector": Object { + "fields": null, + "id": "none", + "name": "none", + "type": ".none", + }, + "created_at": "2019-11-25T21:54:48.952Z", + "created_by": Object { + "email": "testemail@elastic.co", + "full_name": "elastic", + "username": "elastic", + }, + "customFields": Array [], + "description": "This is a brand new case of a bad meanie defacing data", + "duration": null, + "external_service": Object { + "connector_id": "none", + "connector_name": ".jira", + "external_id": "100", + "external_title": "awesome", + "external_url": "http://www.google.com", + "pushed_at": "2019-11-25T21:54:48.952Z", + "pushed_by": Object { + "email": "testemail@elastic.co", + "full_name": "elastic", + "username": "elastic", + }, + }, + "owner": "securitySolution", + "settings": Object { + "syncAlerts": true, + }, + "severity": "medium", + "status": "open", + "tags": Array [ + "defacement", + ], + "title": "Super Bad Security Issue", + "updated_at": "2019-11-25T21:54:48.952Z", + "updated_by": Object { + "email": "testemail@elastic.co", + "full_name": "elastic", + "username": "elastic", + }, + }, + "id": "1", + "references": Array [], + "type": "cases", + }, + ], + } + `); + }); + + it('does not include total_alerts and total_comments fields in the response', async () => { + unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({ + saved_objects: [createCaseSavedObjectResponse({})], + }); + + const res = await service.bulkCreateCases({ + cases: [ + { + ...createCasePostParams({ connector: getNoneCaseConnector() }), + id: '1', + }, + ], + }); + + const theCase = res.saved_objects[0] as SavedObject; + + expect(theCase.attributes).not.toHaveProperty('total_alerts'); + expect(theCase.attributes).not.toHaveProperty('total_comments'); + }); + }); + describe('find', () => { it('includes the connector.id and connector_id field in the response', async () => { const findMockReturn = createSOFindResponse([ @@ -2469,12 +2769,12 @@ describe('CasesService', () => { }); }); - describe('post', () => { + describe('createCase', () => { it('decodes correctly', async () => { unsecuredSavedObjectsClient.create.mockResolvedValue(createCaseSavedObjectResponse()); await expect( - service.postNewCase({ + service.createCase({ attributes: createCasePostParams({ connector: createJiraConnector() }), id: '1', }) @@ -2489,7 +2789,7 @@ describe('CasesService', () => { unsecuredSavedObjectsClient.create.mockResolvedValue({ ...theCase, attributes }); await expect( - service.postNewCase({ + service.createCase({ attributes: createCasePostParams({ connector: createJiraConnector() }), id: '1', }) @@ -2503,7 +2803,7 @@ describe('CasesService', () => { unsecuredSavedObjectsClient.create.mockResolvedValue({ ...theCase, attributes }); await expect( - service.postNewCase({ + service.createCase({ attributes: createCasePostParams({ connector: createJiraConnector() }), id: '1', }) @@ -2567,6 +2867,126 @@ describe('CasesService', () => { }); }); + describe('bulkCreateCases', () => { + it('decodes correctly', async () => { + unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({ + saved_objects: [createCaseSavedObjectResponse()], + }); + + await expect( + service.bulkCreateCases({ + cases: [ + { + ...createCasePostParams({ connector: createJiraConnector() }), + id: '1', + }, + ], + }) + ).resolves.not.toThrow(); + }); + + it.each(Object.keys(attributesToValidateIfMissing))( + 'throws if %s is omitted', + async (key) => { + const theCase = createCaseSavedObjectResponse(); + const attributes = omit({ ...theCase.attributes }, key); + unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({ + saved_objects: [{ ...theCase, attributes }], + }); + + await expect( + service.bulkCreateCases({ + cases: [ + { + ...createCasePostParams({ connector: createJiraConnector() }), + id: '1', + }, + ], + }) + ).rejects.toThrow(`Invalid value "undefined" supplied to "${key}"`); + } + ); + + it('strips out excess attributes', async () => { + const theCase = createCaseSavedObjectResponse(); + const attributes = { ...theCase.attributes, 'not-exists': 'not-exists' }; + unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({ + saved_objects: [{ ...theCase, attributes }], + }); + + await expect( + service.bulkCreateCases({ + cases: [ + { + ...createCasePostParams({ connector: createJiraConnector() }), + id: '1', + }, + ], + }) + ).resolves.toMatchInlineSnapshot(` + Object { + "saved_objects": Array [ + Object { + "attributes": Object { + "assignees": Array [], + "category": null, + "closed_at": null, + "closed_by": null, + "connector": Object { + "fields": null, + "id": "none", + "name": "none", + "type": ".none", + }, + "created_at": "2019-11-25T21:54:48.952Z", + "created_by": Object { + "email": "testemail@elastic.co", + "full_name": "elastic", + "username": "elastic", + }, + "customFields": Array [], + "description": "This is a brand new case of a bad meanie defacing data", + "duration": null, + "external_service": Object { + "connector_id": "none", + "connector_name": ".jira", + "external_id": "100", + "external_title": "awesome", + "external_url": "http://www.google.com", + "pushed_at": "2019-11-25T21:54:48.952Z", + "pushed_by": Object { + "email": "testemail@elastic.co", + "full_name": "elastic", + "username": "elastic", + }, + }, + "owner": "securitySolution", + "settings": Object { + "syncAlerts": true, + }, + "severity": "low", + "status": "open", + "tags": Array [ + "defacement", + ], + "title": "Super Bad Security Issue", + "updated_at": "2019-11-25T21:54:48.952Z", + "updated_by": Object { + "email": "testemail@elastic.co", + "full_name": "elastic", + "username": "elastic", + }, + }, + "id": "1", + "references": Array [], + "type": "cases", + }, + ], + } + `); + }); + }); + describe('patchCase', () => { it('decodes correctly', async () => { unsecuredSavedObjectsClient.update.mockResolvedValue(createUpdateSOResponse()); @@ -2732,7 +3152,7 @@ describe('CasesService', () => { }); describe('Decoding requests', () => { - describe('create case', () => { + describe('createCase', () => { beforeEach(() => { unsecuredSavedObjectsClient.create.mockResolvedValue(createCaseSavedObjectResponse()); }); @@ -2740,7 +3160,7 @@ describe('CasesService', () => { it('decodes correctly the requested attributes', async () => { const attributes = createCasePostParams({ connector: createJiraConnector() }); - await expect(service.postNewCase({ id: 'a', attributes })).resolves.not.toThrow(); + await expect(service.createCase({ id: 'a', attributes })).resolves.not.toThrow(); }); it('throws if title is omitted', async () => { @@ -2748,7 +3168,7 @@ describe('CasesService', () => { unset(attributes, 'title'); await expect( - service.postNewCase({ + service.createCase({ attributes, id: '1', }) @@ -2761,13 +3181,54 @@ describe('CasesService', () => { foo: 'bar', }; - await expect(service.postNewCase({ id: 'a', attributes })).resolves.not.toThrow(); + await expect(service.createCase({ id: 'a', attributes })).resolves.not.toThrow(); const persistedAttributes = unsecuredSavedObjectsClient.create.mock.calls[0][1]; expect(persistedAttributes).not.toHaveProperty('foo'); }); }); + describe('bulkCreateCases', () => { + beforeEach(() => { + unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({ + saved_objects: [createCaseSavedObjectResponse()], + }); + }); + + it('decodes correctly the requested attributes', async () => { + const attributes = createCasePostParams({ connector: createJiraConnector() }); + + await expect( + service.bulkCreateCases({ cases: [{ id: 'a', ...attributes }] }) + ).resolves.not.toThrow(); + }); + + it('throws if title is omitted', async () => { + const attributes = createCasePostParams({ connector: createJiraConnector() }); + unset(attributes, 'title'); + + await expect( + service.bulkCreateCases({ cases: [{ id: '1', ...attributes }] }) + ).rejects.toThrow(`Invalid value "undefined" supplied to "title"`); + }); + + it('remove excess fields', async () => { + const attributes = { + ...createCasePostParams({ connector: createJiraConnector() }), + foo: 'bar', + }; + + await expect( + service.bulkCreateCases({ cases: [{ id: 'a', ...attributes }] }) + ).resolves.not.toThrow(); + + const persistedAttributes = + unsecuredSavedObjectsClient.bulkCreate.mock.calls[0][0][0].attributes; + + expect(persistedAttributes).not.toHaveProperty('foo'); + }); + }); + describe('patch case', () => { beforeEach(() => { unsecuredSavedObjectsClient.update.mockResolvedValue( diff --git a/x-pack/plugins/cases/server/services/cases/index.ts b/x-pack/plugins/cases/server/services/cases/index.ts index 8ead5738d51957..5942f5ce1096cc 100644 --- a/x-pack/plugins/cases/server/services/cases/index.ts +++ b/x-pack/plugins/cases/server/services/cases/index.ts @@ -31,7 +31,11 @@ import { MAX_DOCS_PER_PAGE, } from '../../../common/constants'; import { decodeOrThrow } from '../../../common/api'; -import type { SavedObjectFindOptionsKueryNode, SOWithErrors } from '../../common/types'; +import type { + SavedObjectFindOptionsKueryNode, + SavedObjectsBulkResponseWithErrors, + SOWithErrors, +} from '../../common/types'; import { defaultSortField, flattenCaseSavedObject, isSOError } from '../../common/utils'; import { DEFAULT_PAGE, DEFAULT_PER_PAGE } from '../../routes/api'; import { combineFilters } from '../../client/utils'; @@ -67,10 +71,11 @@ import type { FindCaseCommentsArgs, GetReportersArgs, GetTagsArgs, - PostCaseArgs, + CreateCaseArgs, PatchCaseArgs, PatchCasesArgs, GetCategoryArgs, + BulkCreateCasesArgs, } from './types'; import type { AttachmentTransformedAttributes } from '../../common/types/attachments'; import { bulkDecodeSOAttributes } from '../utils'; @@ -589,13 +594,13 @@ export class CasesService { } } - public async postNewCase({ + public async createCase({ attributes, id, refresh, - }: PostCaseArgs): Promise { + }: CreateCaseArgs): Promise { try { - this.log.debug(`Attempting to POST a new case`); + this.log.debug(`Attempting to create a new case`); const decodedAttributes = decodeOrThrow(CaseTransformedAttributesRt)(attributes); const transformedAttributes = transformAttributesToESModel(decodedAttributes); @@ -614,7 +619,57 @@ export class CasesService { return { ...res, attributes: decodedRes }; } catch (error) { - this.log.error(`Error on POST a new case: ${error}`); + this.log.error(`Error on creating a new case: ${error}`); + throw error; + } + } + + public async bulkCreateCases({ + cases, + refresh, + }: BulkCreateCasesArgs): Promise> { + try { + this.log.debug(`Attempting to bulk create cases`); + + const bulkCreateRequest = cases.map(({ id, ...attributes }) => { + const decodedAttributes = decodeOrThrow(CaseTransformedAttributesRt)(attributes); + + const { attributes: transformedAttributes, referenceHandler } = + transformAttributesToESModel(decodedAttributes); + + transformedAttributes.total_alerts = -1; + transformedAttributes.total_comments = -1; + + return { + type: CASE_SAVED_OBJECT, + id, + attributes: transformedAttributes, + references: referenceHandler.build(), + }; + }); + + const bulkCreateResponse = + await this.unsecuredSavedObjectsClient.bulkCreate( + bulkCreateRequest, + { + refresh, + } + ); + + const res = bulkCreateResponse.saved_objects.map((theCase) => { + if (isSOError(theCase)) { + return theCase; + } + + const transformedCase = transformSavedObjectToExternalModel(theCase); + const decodedRes = decodeOrThrow(CaseTransformedAttributesRt)(transformedCase.attributes); + + return { ...transformedCase, attributes: decodedRes }; + }); + + return { saved_objects: res }; + } catch (error) { + this.log.error(`Case Service: Error on bulk creating cases: ${error}`); throw error; } } diff --git a/x-pack/plugins/cases/server/services/cases/types.ts b/x-pack/plugins/cases/server/services/cases/types.ts index 14f210134e48d4..478525f32d6fa3 100644 --- a/x-pack/plugins/cases/server/services/cases/types.ts +++ b/x-pack/plugins/cases/server/services/cases/types.ts @@ -46,11 +46,15 @@ export interface FindCaseCommentsArgs { options?: SavedObjectFindOptionsKueryNode; } -export interface PostCaseArgs extends IndexRefresh { +export interface CreateCaseArgs extends IndexRefresh { attributes: CaseTransformedAttributes; id: string; } +export interface BulkCreateCasesArgs extends IndexRefresh { + cases: Array<{ id: string } & CaseTransformedAttributes>; +} + export interface PatchCase extends IndexRefresh { caseId: string; updatedAttributes: Partial; diff --git a/x-pack/plugins/cases/server/services/mocks.ts b/x-pack/plugins/cases/server/services/mocks.ts index 7a9507be8b080c..7e2636ffbd6899 100644 --- a/x-pack/plugins/cases/server/services/mocks.ts +++ b/x-pack/plugins/cases/server/services/mocks.ts @@ -55,7 +55,8 @@ export const createCaseServiceMock = (): CaseServiceMock => { getResolveCase: jest.fn(), getTags: jest.fn(), getReporters: jest.fn(), - postNewCase: jest.fn(), + createCase: jest.fn(), + bulkCreateCases: jest.fn(), patchCase: jest.fn(), patchCases: jest.fn(), findCasesGroupedByID: jest.fn(), @@ -101,6 +102,7 @@ const createUserActionPersisterServiceMock = (): CaseUserActionPersisterServiceM bulkCreateAttachmentDeletion: jest.fn(), bulkCreateAttachmentCreation: jest.fn(), createUserAction: jest.fn(), + bulkCreateUserAction: jest.fn(), }; return service as unknown as CaseUserActionPersisterServiceMock; diff --git a/x-pack/plugins/cases/server/services/notifications/email_notification_service.ts b/x-pack/plugins/cases/server/services/notifications/email_notification_service.ts index 73ae62d5822891..e17eb2f22f7bcb 100644 --- a/x-pack/plugins/cases/server/services/notifications/email_notification_service.ts +++ b/x-pack/plugins/cases/server/services/notifications/email_notification_service.ts @@ -13,7 +13,7 @@ import type { UserProfileUserInfo } from '@kbn/user-profile-components'; import { CASE_SAVED_OBJECT, MAX_CONCURRENT_SEARCHES } from '../../../common/constants'; import type { CaseSavedObjectTransformed } from '../../common/types/case'; import { getCaseViewPath } from '../../common/utils'; -import type { NotificationService, NotifyArgs } from './types'; +import type { NotificationService, NotifyAssigneesArgs } from './types'; import { assigneesTemplateRenderer } from './templates/assignees/renderer'; type WithRequiredProperty = T & Required>; @@ -86,7 +86,7 @@ export class EmailNotificationService implements NotificationService { return assigneesTemplateRenderer(theCase, caseUrl); } - public async notifyAssignees({ assignees, theCase }: NotifyArgs) { + public async notifyAssignees({ assignees, theCase }: NotifyAssigneesArgs) { try { if (!this.notifications.isEmailServiceAvailable()) { this.logger.warn('Could not notifying assignees. Email service is not available.'); @@ -139,14 +139,14 @@ export class EmailNotificationService implements NotificationService { } } - public async bulkNotifyAssignees(casesAndAssigneesToNotifyForAssignment: NotifyArgs[]) { + public async bulkNotifyAssignees(casesAndAssigneesToNotifyForAssignment: NotifyAssigneesArgs[]) { if (casesAndAssigneesToNotifyForAssignment.length === 0) { return; } await pMap( casesAndAssigneesToNotifyForAssignment, - (args: NotifyArgs) => this.notifyAssignees(args), + (args: NotifyAssigneesArgs) => this.notifyAssignees(args), { concurrency: MAX_CONCURRENT_SEARCHES, } diff --git a/x-pack/plugins/cases/server/services/notifications/templates/assignees/renderer.test.ts b/x-pack/plugins/cases/server/services/notifications/templates/assignees/renderer.test.ts index ecd0bc08393bb1..fb6509aeceeb4a 100644 --- a/x-pack/plugins/cases/server/services/notifications/templates/assignees/renderer.test.ts +++ b/x-pack/plugins/cases/server/services/notifications/templates/assignees/renderer.test.ts @@ -6,7 +6,7 @@ */ import { mockCases } from '../../../../mocks'; -import { getByText } from '@testing-library/dom'; +import { getByText } from '@testing-library/react'; import { assigneesTemplateRenderer } from './renderer'; import type { CaseSavedObjectTransformed } from '../../../../common/types/case'; diff --git a/x-pack/plugins/cases/server/services/notifications/templates/assignees/template.test.ts b/x-pack/plugins/cases/server/services/notifications/templates/assignees/template.test.ts index b888cd6017b244..79589f4e58bef2 100644 --- a/x-pack/plugins/cases/server/services/notifications/templates/assignees/template.test.ts +++ b/x-pack/plugins/cases/server/services/notifications/templates/assignees/template.test.ts @@ -6,7 +6,7 @@ */ import { mockCases } from '../../../../mocks'; -import { getByText } from '@testing-library/dom'; +import { getByText } from '@testing-library/react'; import { assigneesTemplateRenderer } from './renderer'; import type { CaseSavedObjectTransformed } from '../../../../common/types/case'; diff --git a/x-pack/plugins/cases/server/services/notifications/types.ts b/x-pack/plugins/cases/server/services/notifications/types.ts index d89184f03b01c9..7cbbc33cf808e1 100644 --- a/x-pack/plugins/cases/server/services/notifications/types.ts +++ b/x-pack/plugins/cases/server/services/notifications/types.ts @@ -8,12 +8,12 @@ import type { CaseAssignees } from '../../../common/types/domain'; import type { CaseSavedObjectTransformed } from '../../common/types/case'; -export interface NotifyArgs { +export interface NotifyAssigneesArgs { assignees: CaseAssignees; theCase: CaseSavedObjectTransformed; } export interface NotificationService { - notifyAssignees: (args: NotifyArgs) => Promise; - bulkNotifyAssignees: (args: NotifyArgs[]) => Promise; + notifyAssignees: (args: NotifyAssigneesArgs) => Promise; + bulkNotifyAssignees: (args: NotifyAssigneesArgs[]) => Promise; } diff --git a/x-pack/plugins/cases/server/services/test_utils.ts b/x-pack/plugins/cases/server/services/test_utils.ts index 022a868ee0d9b6..cd0f16d66e4cbc 100644 --- a/x-pack/plugins/cases/server/services/test_utils.ts +++ b/x-pack/plugins/cases/server/services/test_utils.ts @@ -26,6 +26,7 @@ import type { ConnectorPersistedFields } from '../common/types/connectors'; import type { CasePersistedAttributes } from '../common/types/case'; import { CasePersistedSeverity, CasePersistedStatus } from '../common/types/case'; import type { ExternalServicePersisted } from '../common/types/external_service'; +import type { SOWithErrors } from '../common/types'; /** * This is only a utility interface to help with constructing test cases. After the migration, the ES format will no longer @@ -271,3 +272,14 @@ export const mockPointInTimeFinder = }, }); }; + +export const createErrorSO = (type: string): SOWithErrors => ({ + id: '1', + type, + error: { + error: 'error', + message: 'message', + statusCode: 500, + }, + references: [], +}); diff --git a/x-pack/plugins/cases/server/services/user_actions/index.test.ts b/x-pack/plugins/cases/server/services/user_actions/index.test.ts index 905e94bc11d8a4..20c06f2701fedc 100644 --- a/x-pack/plugins/cases/server/services/user_actions/index.test.ts +++ b/x-pack/plugins/cases/server/services/user_actions/index.test.ts @@ -107,9 +107,11 @@ describe('CaseUserActionService', () => { describe('create case', () => { it('creates a create case user action', async () => { await service.creator.createUserAction({ - ...commonArgs, - payload: casePayload, - type: UserActionTypes.create_case, + userAction: { + ...commonArgs, + payload: casePayload, + type: UserActionTypes.create_case, + }, }); expect(unsecuredSavedObjectsClient.create).toHaveBeenCalledWith( @@ -159,9 +161,11 @@ describe('CaseUserActionService', () => { it('logs a create case user action', async () => { await service.creator.createUserAction({ - ...commonArgs, - payload: casePayload, - type: UserActionTypes.create_case, + userAction: { + ...commonArgs, + payload: casePayload, + type: UserActionTypes.create_case, + }, }); expect(mockAuditLogger.log).toBeCalledTimes(1); @@ -193,9 +197,11 @@ describe('CaseUserActionService', () => { describe('status', () => { it('creates an update status user action', async () => { await service.creator.createUserAction({ - ...commonArgs, - payload: { status: CaseStatuses.closed }, - type: UserActionTypes.status, + userAction: { + ...commonArgs, + payload: { status: CaseStatuses.closed }, + type: UserActionTypes.status, + }, }); expect(unsecuredSavedObjectsClient.create).toHaveBeenCalledWith( @@ -218,9 +224,11 @@ describe('CaseUserActionService', () => { it('logs an update status user action', async () => { await service.creator.createUserAction({ - ...commonArgs, - payload: { status: CaseStatuses.closed }, - type: UserActionTypes.status, + userAction: { + ...commonArgs, + payload: { status: CaseStatuses.closed }, + type: UserActionTypes.status, + }, }); expect(mockAuditLogger.log).toBeCalledTimes(1); @@ -253,9 +261,11 @@ describe('CaseUserActionService', () => { describe('severity', () => { it('creates an update severity user action', async () => { await service.creator.createUserAction({ - ...commonArgs, - payload: { severity: CaseSeverity.MEDIUM }, - type: UserActionTypes.severity, + userAction: { + ...commonArgs, + payload: { severity: CaseSeverity.MEDIUM }, + type: UserActionTypes.severity, + }, }); expect(unsecuredSavedObjectsClient.create).toHaveBeenCalledWith( @@ -278,9 +288,11 @@ describe('CaseUserActionService', () => { it('logs an update severity user action', async () => { await service.creator.createUserAction({ - ...commonArgs, - payload: { severity: CaseSeverity.MEDIUM }, - type: UserActionTypes.severity, + userAction: { + ...commonArgs, + payload: { severity: CaseSeverity.MEDIUM }, + type: UserActionTypes.severity, + }, }); expect(mockAuditLogger.log).toBeCalledTimes(1); @@ -313,9 +325,11 @@ describe('CaseUserActionService', () => { describe('push', () => { it('creates a push user action', async () => { await service.creator.createUserAction({ - ...commonArgs, - payload: { externalService }, - type: UserActionTypes.pushed, + userAction: { + ...commonArgs, + payload: { externalService }, + type: UserActionTypes.pushed, + }, }); expect(unsecuredSavedObjectsClient.create).toHaveBeenCalledWith( @@ -357,9 +371,11 @@ describe('CaseUserActionService', () => { it('logs a push user action', async () => { await service.creator.createUserAction({ - ...commonArgs, - payload: { externalService }, - type: UserActionTypes.pushed, + userAction: { + ...commonArgs, + payload: { externalService }, + type: UserActionTypes.pushed, + }, }); expect(mockAuditLogger.log).toBeCalledTimes(1); @@ -396,11 +412,13 @@ describe('CaseUserActionService', () => { [UserActionActions.update], ])('creates a comment user action of action: %s', async (action) => { await service.creator.createUserAction({ - ...commonArgs, - type: UserActionTypes.comment, - action, - attachmentId: 'test-id', - payload: { attachment: comment }, + userAction: { + ...commonArgs, + type: UserActionTypes.comment, + action, + attachmentId: 'test-id', + payload: { attachment: comment }, + }, }); expect(unsecuredSavedObjectsClient.create).toHaveBeenCalledWith( @@ -438,11 +456,13 @@ describe('CaseUserActionService', () => { [UserActionActions.update], ])('logs a comment user action of action: %s', async (action) => { await service.creator.createUserAction({ - ...commonArgs, - type: UserActionTypes.comment, - action, - attachmentId: 'test-id', - payload: { attachment: comment }, + userAction: { + ...commonArgs, + type: UserActionTypes.comment, + action, + attachmentId: 'test-id', + payload: { attachment: comment }, + }, }); expect(mockAuditLogger.log).toBeCalledTimes(1); diff --git a/x-pack/plugins/cases/server/services/user_actions/operations/create.test.ts b/x-pack/plugins/cases/server/services/user_actions/operations/create.test.ts index 853c4969237f5c..833e8676a26195 100644 --- a/x-pack/plugins/cases/server/services/user_actions/operations/create.test.ts +++ b/x-pack/plugins/cases/server/services/user_actions/operations/create.test.ts @@ -15,7 +15,11 @@ import { set, unset } from 'lodash'; import { createConnectorObject } from '../../test_utils'; import { UserActionPersister } from './create'; import { createUserActionSO } from '../test_utils'; -import type { BulkCreateAttachmentUserAction, CreateUserActionClient } from '../types'; +import type { + BuilderParameters, + BulkCreateAttachmentUserAction, + CreateUserActionArgs, +} from '../types'; import type { UserActionPersistedAttributes } from '../../../common/types/user_actions'; import { getAssigneesAddedRemovedUserActions, @@ -65,16 +69,19 @@ describe('UserActionPersister', () => { jest.useRealTimers(); }); - const getRequest = () => + const getRequest = (overrides = {}) => ({ - action: 'update' as const, - type: 'connector' as const, - caseId: 'test', - payload: { connector: createConnectorObject().connector }, - connectorId: '1', - owner: 'cases', - user: { email: '', full_name: '', username: '' }, - } as CreateUserActionClient<'connector'>); + userAction: { + action: 'update' as const, + type: 'connector' as const, + caseId: 'test', + payload: { connector: createConnectorObject().connector }, + connectorId: '1', + owner: 'cases', + user: { email: '', full_name: '', username: '' }, + ...overrides, + }, + } as CreateUserActionArgs); const getBulkCreateAttachmentRequest = (): BulkCreateAttachmentUserAction => ({ caseId: 'test', @@ -107,7 +114,7 @@ describe('UserActionPersister', () => { it('throws if fields is omitted', async () => { const req = getRequest(); - unset(req, 'payload.connector.fields'); + unset(req, 'userAction.payload.connector.fields'); await expect(persister.createUserAction(req)).rejects.toThrow( 'Invalid value "undefined" supplied to "payload,connector,fields"' @@ -141,6 +148,56 @@ describe('UserActionPersister', () => { }); }); + it('decodes correctly the requested attributes', async () => { + await expect( + persister.bulkCreateUserAction({ + userActions: [getRequest().userAction], + }) + ).resolves.not.toThrow(); + }); + + it('throws if owner is omitted', async () => { + const req = getRequest().userAction; + unset(req, 'owner'); + + await expect( + persister.bulkCreateUserAction({ + userActions: [req], + }) + ).rejects.toThrow('Invalid value "undefined" supplied to "owner"'); + }); + + it('strips out excess attributes', async () => { + const req = getRequest().userAction; + set(req, 'payload.foo', 'bar'); + + await expect( + persister.bulkCreateUserAction({ + userActions: [req], + }) + ).resolves.not.toThrow(); + + const persistedAttributes = unsecuredSavedObjectsClient.bulkCreate.mock.calls[0][0][0] + .attributes as UserActionPersistedAttributes; + + expect(persistedAttributes.payload).not.toHaveProperty('foo'); + }); + }); + + describe('bulkCreateUserAction', () => { + beforeEach(() => { + unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({ + saved_objects: [ + { + attributes: createUserActionSO(), + id: '1', + type: CASE_USER_ACTION_SAVED_OBJECT, + references: [], + }, + ], + }); + }); + it('decodes correctly the requested attributes', async () => { await expect( persister.bulkCreateAttachmentCreation(getBulkCreateAttachmentRequest()) @@ -524,4 +581,103 @@ describe('UserActionPersister', () => { }); }); }); + + describe('bulkCreateUserAction', () => { + beforeEach(() => { + unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({ + saved_objects: [ + { + attributes: [createUserActionSO()], + id: '1', + type: CASE_USER_ACTION_SAVED_OBJECT, + references: [], + }, + { + attributes: [createUserActionSO()], + id: '2', + type: CASE_USER_ACTION_SAVED_OBJECT, + references: [], + }, + ], + }); + }); + + it('bulk creates the user actions correctly', async () => { + const connectorUserAction = getRequest().userAction; + const titleUserAction = getRequest<'title'>({ + type: 'title', + payload: { title: 'my title' }, + }).userAction; + + await persister.bulkCreateUserAction({ + userActions: [connectorUserAction, titleUserAction], + }); + + expect(unsecuredSavedObjectsClient.bulkCreate.mock.calls[0][0]).toMatchInlineSnapshot(` + Array [ + Object { + "attributes": Object { + "action": "update", + "created_at": "2022-01-09T22:00:00.000Z", + "created_by": Object { + "email": "", + "full_name": "", + "username": "", + }, + "owner": "cases", + "payload": Object { + "connector": Object { + "fields": Object { + "issueType": "bug", + "parent": "2", + "priority": "high", + }, + "name": ".jira", + "type": ".jira", + }, + }, + "type": "connector", + }, + "references": Array [ + Object { + "id": "test", + "name": "associated-cases", + "type": "cases", + }, + Object { + "id": "1", + "name": "connectorId", + "type": "action", + }, + ], + "type": "cases-user-actions", + }, + Object { + "attributes": Object { + "action": "update", + "created_at": "2022-01-09T22:00:00.000Z", + "created_by": Object { + "email": "", + "full_name": "", + "username": "", + }, + "owner": "cases", + "payload": Object { + "title": "my title", + }, + "type": "title", + }, + "references": Array [ + Object { + "id": "test", + "name": "associated-cases", + "type": "cases", + }, + ], + "type": "cases-user-actions", + }, + ] + `); + }); + }); }); diff --git a/x-pack/plugins/cases/server/services/user_actions/operations/create.ts b/x-pack/plugins/cases/server/services/user_actions/operations/create.ts index 1f2ba3dc8f0463..e66f375e391081 100644 --- a/x-pack/plugins/cases/server/services/user_actions/operations/create.ts +++ b/x-pack/plugins/cases/server/services/user_actions/operations/create.ts @@ -30,7 +30,6 @@ import type { BulkCreateBulkUpdateCaseUserActions, CommonUserActionArgs, CreatePayloadFunction, - CreateUserActionClient, CreateUserActionES, GetUserActionItemByDifference, PostCaseUserActionArgs, @@ -38,6 +37,8 @@ import type { TypedUserActionDiffedItems, UserActionEvent, UserActionsDict, + CreateUserActionArgs, + BulkCreateUserActionArgs, } from '../types'; import { isAssigneesArray, isCustomFieldsArray, isStringArray } from '../type_guards'; import type { IndexRefresh } from '../../types'; @@ -375,21 +376,16 @@ export class UserActionPersister { } public async createUserAction({ - action, - type, - caseId, - user, - owner, - payload, - connectorId, - attachmentId, + userAction, refresh, - }: CreateUserActionClient): Promise { + }: CreateUserActionArgs): Promise { + const { action, type, caseId, user, owner, payload, connectorId, attachmentId } = userAction; + try { this.context.log.debug(`Attempting to create a user action of type: ${type}`); const userActionBuilder = this.builderFactory.getBuilder(type); - const userAction = userActionBuilder?.build({ + const userActionPayload = userActionBuilder?.build({ action, caseId, user, @@ -399,8 +395,8 @@ export class UserActionPersister { payload, }); - if (userAction) { - await this.createAndLog({ userAction, refresh }); + if (userActionPayload) { + await this.createAndLog({ userAction: userActionPayload, refresh }); } } catch (error) { this.context.log.error(`Error on creating user action of type: ${type}. Error: ${error}`); @@ -408,6 +404,45 @@ export class UserActionPersister { } } + public async bulkCreateUserAction({ + userActions, + refresh, + }: BulkCreateUserActionArgs): Promise { + try { + this.context.log.debug(`Attempting to bulk create a user actions`); + + if (userActions.length <= 0) { + return; + } + + const userActionsPayload = userActions + .map(({ action, type, caseId, user, owner, payload, connectorId, attachmentId }) => { + const userActionBuilder = this.builderFactory.getBuilder(type); + const userAction = userActionBuilder?.build({ + action, + caseId, + user, + owner, + connectorId, + attachmentId, + payload, + }); + + if (userAction == null) { + return null; + } + + return userAction; + }) + .filter(Boolean) as UserActionEvent[]; + + await this.bulkCreateAndLog({ userActions: userActionsPayload, refresh }); + } catch (error) { + this.context.log.error(`Error on bulk creating user actions. Error: ${error}`); + throw error; + } + } + private async createAndLog({ userAction, refresh, diff --git a/x-pack/plugins/cases/server/services/user_actions/types.ts b/x-pack/plugins/cases/server/services/user_actions/types.ts index 42ebb7e4135821..4b99651300852c 100644 --- a/x-pack/plugins/cases/server/services/user_actions/types.ts +++ b/x-pack/plugins/cases/server/services/user_actions/types.ts @@ -312,9 +312,13 @@ export interface BulkCreateAttachmentUserAction attachments: Array<{ id: string; owner: string; attachment: AttachmentRequest }>; } -export type CreateUserActionClient = CreateUserAction & - CommonUserActionArgs & - IndexRefresh; +export type CreateUserActionArgs = { + userAction: CreateUserAction & CommonUserActionArgs; +} & IndexRefresh; + +export type BulkCreateUserActionArgs = { + userActions: Array & CommonUserActionArgs>; +} & IndexRefresh; export interface CreateUserActionES extends IndexRefresh { attributes: T; diff --git a/x-pack/plugins/cloud_security_posture/public/components/compliance_score_bar.tsx b/x-pack/plugins/cloud_security_posture/public/components/compliance_score_bar.tsx index 3f6c78fe6b2ef1..8f866079ab160a 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/compliance_score_bar.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/compliance_score_bar.tsx @@ -45,7 +45,7 @@ export const ComplianceScoreBar = ({ gutterSize="none" alignItems="center" justifyContent="flexEnd" - style={{ gap: euiTheme.size.s }} + style={{ gap: euiTheme.size.xs }} > - + - +
    +
  1. + +
  2. { return ( <> - + diff --git a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/summary_section.test.tsx b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/summary_section.test.tsx index 0545d4f3bb4298..04c83825295931 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/summary_section.test.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/summary_section.test.tsx @@ -6,13 +6,12 @@ */ import React from 'react'; -import { render } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import { expectIdsInDoc } from '../../../test/utils'; import { DASHBOARD_COUNTER_CARDS } from '../test_subjects'; import { SummarySection } from './summary_section'; import { mockDashboardData } from '../mock'; import { TestProvider } from '../../../test/test_provider'; -import { screen } from '@testing-library/react'; import { KSPM_POLICY_TEMPLATE } from '../../../../common/constants'; describe('', () => { diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/findings_flyout/findings_flyout.tsx b/x-pack/plugins/cloud_security_posture/public/pages/configurations/findings_flyout/findings_flyout.tsx index 524e893092e53a..a6bb3b5bc08e3e 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/configurations/findings_flyout/findings_flyout.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/configurations/findings_flyout/findings_flyout.tsx @@ -22,6 +22,7 @@ import { EuiIcon, EuiPagination, EuiFlyoutFooter, + EuiToolTip, } from '@elastic/eui'; import { assertNever } from '@kbn/std'; import { i18n } from '@kbn/i18n'; @@ -98,7 +99,9 @@ export const CisKubernetesIcons = ({ }) => ( - + + + diff --git a/x-pack/plugins/data_visualizer/public/application/common/hooks/use_document_count_stats.ts b/x-pack/plugins/data_visualizer/public/application/common/hooks/use_document_count_stats.ts index 87a9a37d4d2c5c..93bf37eb839161 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/hooks/use_document_count_stats.ts +++ b/x-pack/plugins/data_visualizer/public/application/common/hooks/use_document_count_stats.ts @@ -10,7 +10,7 @@ import { stringHash } from '@kbn/ml-string-hash'; import { extractErrorProperties } from '@kbn/ml-error-utils'; import { Query } from '@kbn/es-query'; import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { SignificantTerm } from '@kbn/ml-agg-utils'; +import { SignificantItem } from '@kbn/ml-agg-utils'; import { createRandomSamplerWrapper, RandomSampler, @@ -50,8 +50,8 @@ export interface DocumentStatsSearchStrategyParams { timeFieldName?: string; runtimeFieldMap?: estypes.MappingRuntimeFields; fieldsToFetch?: string[]; - selectedSignificantTerm?: SignificantTerm; - includeSelectedSignificantTerm?: boolean; + selectedSignificantItem?: SignificantItem; + includeSelectedSignificantItem?: boolean; trackTotalHits?: boolean; } @@ -167,8 +167,8 @@ export interface DocumentStatsSearchStrategyParams { timeFieldName?: string; runtimeFieldMap?: estypes.MappingRuntimeFields; fieldsToFetch?: string[]; - selectedSignificantTerm?: SignificantTerm; - includeSelectedSignificantTerm?: boolean; + selectedSignificantItem?: SignificantItem; + includeSelectedSignificantItem?: boolean; trackTotalHits?: boolean; } diff --git a/x-pack/plugins/dataset_quality/README.md b/x-pack/plugins/dataset_quality/README.md new file mode 100755 index 00000000000000..cd5bfc30658d4b --- /dev/null +++ b/x-pack/plugins/dataset_quality/README.md @@ -0,0 +1,3 @@ +# Dataset Quality + +In order to make ongoing maintenance of log collection easy we want to introduce the concept of dataset quality, where users can easily get an overview on the datasets they have with information such as integration, size, last activity, among others. diff --git a/x-pack/plugins/dataset_quality/common/index.ts b/x-pack/plugins/dataset_quality/common/index.ts new file mode 100644 index 00000000000000..440e8780e6bf52 --- /dev/null +++ b/x-pack/plugins/dataset_quality/common/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export type { DatasetQualityConfig } from './plugin_config'; diff --git a/x-pack/plugins/dataset_quality/common/plugin_config.ts b/x-pack/plugins/dataset_quality/common/plugin_config.ts new file mode 100644 index 00000000000000..d0dac3d6cfdbb7 --- /dev/null +++ b/x-pack/plugins/dataset_quality/common/plugin_config.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface DatasetQualityConfig {} diff --git a/x-pack/plugins/dataset_quality/jest.config.js b/x-pack/plugins/dataset_quality/jest.config.js new file mode 100644 index 00000000000000..fdd27a97fc347b --- /dev/null +++ b/x-pack/plugins/dataset_quality/jest.config.js @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/x-pack/plugins/dataset_quality'], + coverageDirectory: '/target/kibana-coverage/jest/x-pack/plugins/dataset_quality', + coverageReporters: ['text', 'html'], + collectCoverageFrom: ['/x-pack/plugins/dataset_quality/{common,public}/**/*.{ts,tsx}'], +}; diff --git a/x-pack/plugins/dataset_quality/kibana.jsonc b/x-pack/plugins/dataset_quality/kibana.jsonc new file mode 100644 index 00000000000000..133537a76d831c --- /dev/null +++ b/x-pack/plugins/dataset_quality/kibana.jsonc @@ -0,0 +1,16 @@ +{ + "type": "plugin", + "id": "@kbn/dataset-quality-plugin", + "owner": "@elastic/obs-ux-logs-team", + "description": "This plugin introduces the concept of dataset quality, where users can easily get an overview on the datasets they have.", + "plugin": { + "id": "datasetQuality", + "server": true, + "browser": true, + "configPath": ["xpack", "datasetQuality"], + "requiredPlugins": ["data", "kibanaReact", "kibanaUtils", "controls", "embeddable", "share"], + "optionalPlugins": [], + "requiredBundles": [], + "extraPublicDirs": ["common"] + } +} diff --git a/x-pack/plugins/dataset_quality/public/index.ts b/x-pack/plugins/dataset_quality/public/index.ts new file mode 100644 index 00000000000000..339be1ec1de9b0 --- /dev/null +++ b/x-pack/plugins/dataset_quality/public/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { PluginInitializerContext } from '@kbn/core/public'; +import { DatasetQualityConfig } from '../common/plugin_config'; +import { DatasetQualityPlugin } from './plugin'; + +export type { DatasetQualityPluginSetup, DatasetQualityPluginStart } from './types'; + +export function plugin(context: PluginInitializerContext) { + return new DatasetQualityPlugin(context); +} diff --git a/x-pack/plugins/dataset_quality/public/plugin.ts b/x-pack/plugins/dataset_quality/public/plugin.ts new file mode 100644 index 00000000000000..520c02481cd604 --- /dev/null +++ b/x-pack/plugins/dataset_quality/public/plugin.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from '@kbn/core/public'; +import { + DatasetQualityPluginSetup, + DatasetQualityPluginStart, + DatasetQualitySetupDependencies, + DatasetQualityStartDependencies, +} from './types'; + +export class DatasetQualityPlugin + implements Plugin +{ + constructor(context: PluginInitializerContext) {} + + public setup(core: CoreSetup, plugins: DatasetQualitySetupDependencies) { + return {}; + } + + public start( + core: CoreStart, + plugins: DatasetQualityStartDependencies + ): DatasetQualityPluginStart { + return {}; + } +} diff --git a/x-pack/plugins/dataset_quality/public/types.ts b/x-pack/plugins/dataset_quality/public/types.ts new file mode 100644 index 00000000000000..2d57bd6bb1b2e4 --- /dev/null +++ b/x-pack/plugins/dataset_quality/public/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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface DatasetQualityPluginSetup {} + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface DatasetQualityPluginStart {} + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface DatasetQualityStartDependencies {} + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface DatasetQualitySetupDependencies {} diff --git a/x-pack/plugins/dataset_quality/server/index.ts b/x-pack/plugins/dataset_quality/server/index.ts new file mode 100644 index 00000000000000..17b028aa19431b --- /dev/null +++ b/x-pack/plugins/dataset_quality/server/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { DatasetQualityServerPlugin } from './plugin'; + +export const plugin = () => new DatasetQualityServerPlugin(); diff --git a/x-pack/plugins/dataset_quality/server/plugin.ts b/x-pack/plugins/dataset_quality/server/plugin.ts new file mode 100644 index 00000000000000..b673fed3ad622b --- /dev/null +++ b/x-pack/plugins/dataset_quality/server/plugin.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Plugin } from '@kbn/core/server'; + +export class DatasetQualityServerPlugin implements Plugin { + setup() {} + + start() {} +} diff --git a/x-pack/plugins/dataset_quality/tsconfig.json b/x-pack/plugins/dataset_quality/tsconfig.json new file mode 100644 index 00000000000000..efe7ece5983ac8 --- /dev/null +++ b/x-pack/plugins/dataset_quality/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types" + }, + "include": [ + "../../../typings/**/*", + "common/**/*", + "public/**/*", + "server/**/*.ts", + "../../typings/**/*" + ], + "kbn_references": [ + "@kbn/core", + ], + "exclude": ["target/**/*"] +} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/engine_assignment_selector.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/engine_assignment_selector.test.tsx index a8f8b2af988f29..74ca02e1d91dba 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/engine_assignment_selector.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/engine_assignment_selector.test.tsx @@ -13,7 +13,7 @@ import { engines } from '../../__mocks__/engines.mock'; import React from 'react'; -import { waitFor } from '@testing-library/dom'; +import { waitFor } from '@testing-library/react'; import { shallow } from 'enzyme'; import { EuiComboBox, EuiComboBoxOptionOption, EuiRadioGroup } from '@elastic/eui'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/group_assignment_selector.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/group_assignment_selector.test.tsx index 9312b1b6bbdb27..1cea94e434de11 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/group_assignment_selector.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/group_assignment_selector.test.tsx @@ -11,7 +11,7 @@ import { setMockActions, setMockValues } from '../../../__mocks__/kea_logic'; import React from 'react'; -import { waitFor } from '@testing-library/dom'; +import { waitFor } from '@testing-library/react'; import { shallow } from 'enzyme'; import { EuiComboBox, EuiComboBoxOptionOption, EuiRadioGroup } from '@elastic/eui'; diff --git a/x-pack/plugins/enterprise_search/server/plugin.ts b/x-pack/plugins/enterprise_search/server/plugin.ts index 4a5e3689ded5c4..baaa772989eb20 100644 --- a/x-pack/plugins/enterprise_search/server/plugin.ts +++ b/x-pack/plugins/enterprise_search/server/plugin.ts @@ -103,7 +103,9 @@ export interface PluginsStart { export interface RouteDependencies { config: ConfigType; enterpriseSearchRequestHandler: IEnterpriseSearchRequestHandler; + getSavedObjectsService?(): SavedObjectsServiceStart; + log: Logger; ml?: MlPluginSetup; router: IRouter; @@ -112,6 +114,7 @@ export interface RouteDependencies { export class EnterpriseSearchPlugin implements Plugin { private readonly config: ConfigType; private readonly logger: Logger; + /** * Exposed services */ @@ -177,42 +180,47 @@ export class EnterpriseSearchPlugin implements Plugin { /** * Register user access to the Enterprise Search plugins */ - capabilities.registerSwitcher(async (request: KibanaRequest) => { - const [, { spaces }] = await getStartServices(); - - const dependencies = { config, security, spaces, request, log, ml }; - - const { hasAppSearchAccess, hasWorkplaceSearchAccess } = await checkAccess(dependencies); - const showEnterpriseSearch = - hasAppSearchAccess || hasWorkplaceSearchAccess || !config.canDeployEntSearch; - - return { - navLinks: { - enterpriseSearch: showEnterpriseSearch, - enterpriseSearchContent: showEnterpriseSearch, - enterpriseSearchAnalytics: showEnterpriseSearch, - enterpriseSearchApplications: showEnterpriseSearch, - enterpriseSearchAISearch: showEnterpriseSearch, - enterpriseSearchVectorSearch: showEnterpriseSearch, - enterpriseSearchElasticsearch: showEnterpriseSearch, - appSearch: hasAppSearchAccess && config.canDeployEntSearch, - workplaceSearch: hasWorkplaceSearchAccess && config.canDeployEntSearch, - searchExperiences: showEnterpriseSearch, - }, - catalogue: { - enterpriseSearch: showEnterpriseSearch, - enterpriseSearchContent: showEnterpriseSearch, - enterpriseSearchAnalytics: showEnterpriseSearch, - enterpriseSearchApplications: showEnterpriseSearch, - enterpriseSearchAISearch: showEnterpriseSearch, - enterpriseSearchVectorSearch: showEnterpriseSearch, - enterpriseSearchElasticsearch: showEnterpriseSearch, - appSearch: hasAppSearchAccess && config.canDeployEntSearch, - workplaceSearch: hasWorkplaceSearchAccess && config.canDeployEntSearch, - searchExperiences: showEnterpriseSearch, - }, - }; - }); + capabilities.registerSwitcher( + async (request: KibanaRequest) => { + const [, { spaces }] = await getStartServices(); + + const dependencies = { config, security, spaces, request, log, ml }; + + const { hasAppSearchAccess, hasWorkplaceSearchAccess } = await checkAccess(dependencies); + const showEnterpriseSearch = + hasAppSearchAccess || hasWorkplaceSearchAccess || !config.canDeployEntSearch; + + return { + navLinks: { + enterpriseSearch: showEnterpriseSearch, + enterpriseSearchContent: showEnterpriseSearch, + enterpriseSearchAnalytics: showEnterpriseSearch, + enterpriseSearchApplications: showEnterpriseSearch, + enterpriseSearchAISearch: showEnterpriseSearch, + enterpriseSearchVectorSearch: showEnterpriseSearch, + enterpriseSearchElasticsearch: showEnterpriseSearch, + appSearch: hasAppSearchAccess && config.canDeployEntSearch, + workplaceSearch: hasWorkplaceSearchAccess && config.canDeployEntSearch, + searchExperiences: showEnterpriseSearch, + }, + catalogue: { + enterpriseSearch: showEnterpriseSearch, + enterpriseSearchContent: showEnterpriseSearch, + enterpriseSearchAnalytics: showEnterpriseSearch, + enterpriseSearchApplications: showEnterpriseSearch, + enterpriseSearchAISearch: showEnterpriseSearch, + enterpriseSearchVectorSearch: showEnterpriseSearch, + enterpriseSearchElasticsearch: showEnterpriseSearch, + appSearch: hasAppSearchAccess && config.canDeployEntSearch, + workplaceSearch: hasWorkplaceSearchAccess && config.canDeployEntSearch, + searchExperiences: showEnterpriseSearch, + }, + }; + }, + { + capabilityPath: ['navLinks.*', 'catalogue.*'], + } + ); /** * Register routes diff --git a/x-pack/plugins/exploratory_view/kibana.jsonc b/x-pack/plugins/exploratory_view/kibana.jsonc index e541ad78586505..b8041aaec89df7 100644 --- a/x-pack/plugins/exploratory_view/kibana.jsonc +++ b/x-pack/plugins/exploratory_view/kibana.jsonc @@ -1,7 +1,7 @@ { "type": "plugin", "id": "@kbn/exploratory-view-plugin", - "owner": "@elastic/uptime", + "owner": "@elastic/obs-ux-infra_services-team", "plugin": { "id": "exploratoryView", "server": false, diff --git a/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/components/action_menu/action_menu.test.tsx b/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/components/action_menu/action_menu.test.tsx index c72899d8041c70..4e81ca840199fc 100644 --- a/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/components/action_menu/action_menu.test.tsx +++ b/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/components/action_menu/action_menu.test.tsx @@ -6,7 +6,7 @@ */ import { render } from '../../rtl_helpers'; -import { fireEvent, screen } from '@testing-library/dom'; +import { fireEvent, screen } from '@testing-library/react'; import React from 'react'; import { sampleAttribute } from '../../configurations/test_data/sample_attribute'; import * as pluginHook from '../../../../../hooks/use_plugin_context'; diff --git a/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/exploratory_view.test.tsx b/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/exploratory_view.test.tsx index 443a6eb846cd8b..7b4e0cb5cc57f3 100644 --- a/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/exploratory_view.test.tsx +++ b/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/exploratory_view.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { screen } from '@testing-library/dom'; +import { screen } from '@testing-library/react'; import { render, mockAppDataView } from './rtl_helpers'; import { ExploratoryView } from './exploratory_view'; import * as obsvDataViews from '../../../utils/observability_data_views/observability_data_views'; diff --git a/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/header/add_to_case_action.test.tsx b/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/header/add_to_case_action.test.tsx index a4d8a88507e82d..ffccfdf6db3f29 100644 --- a/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/header/add_to_case_action.test.tsx +++ b/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/header/add_to_case_action.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { render, forNearestButton } from '../rtl_helpers'; -import { fireEvent } from '@testing-library/dom'; +import { fireEvent } from '@testing-library/react'; import { AddToCaseAction } from './add_to_case_action'; import * as useCaseHook from '../hooks/use_add_to_case'; import * as datePicker from '../components/date_range_picker'; diff --git a/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/header/chart_creation_info.test.tsx b/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/header/chart_creation_info.test.tsx index 570362a63c33f3..f8da480072ab32 100644 --- a/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/header/chart_creation_info.test.tsx +++ b/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/header/chart_creation_info.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { screen } from '@testing-library/dom'; +import { screen } from '@testing-library/react'; import { render } from '../rtl_helpers'; import { ChartCreationInfo } from './chart_creation_info'; diff --git a/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/hooks/use_add_to_case.test.tsx b/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/hooks/use_add_to_case.test.tsx index 9ab9d00d2bc821..3821c703e7cee8 100644 --- a/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/hooks/use_add_to_case.test.tsx +++ b/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/hooks/use_add_to_case.test.tsx @@ -9,7 +9,7 @@ import { useAddToCase } from './use_add_to_case'; import React, { useEffect } from 'react'; import { render } from '../rtl_helpers'; import { EuiButton } from '@elastic/eui'; -import { fireEvent } from '@testing-library/dom'; +import { fireEvent } from '@testing-library/react'; import { act } from '@testing-library/react'; describe('useAddToCase', function () { diff --git a/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/views/add_series_button.test.tsx b/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/views/add_series_button.test.tsx index 978296a295efc3..65f80245c2ceb8 100644 --- a/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/views/add_series_button.test.tsx +++ b/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/views/add_series_button.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { screen, waitFor, fireEvent } from '@testing-library/dom'; +import { screen, waitFor, fireEvent } from '@testing-library/react'; import { render } from '../rtl_helpers'; import { AddSeriesButton } from './add_series_button'; import { DEFAULT_TIME, ReportTypes } from '../configurations/constants'; diff --git a/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/views/view_actions.test.tsx b/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/views/view_actions.test.tsx index df709c94abcde2..ae2d2ce65d91c8 100644 --- a/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/views/view_actions.test.tsx +++ b/x-pack/plugins/exploratory_view/public/components/shared/exploratory_view/views/view_actions.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { screen, waitFor, fireEvent } from '@testing-library/dom'; +import { screen, waitFor, fireEvent } from '@testing-library/react'; import { render } from '../rtl_helpers'; import * as hooks from '../hooks/use_series_storage'; import { ViewActions } from './view_actions'; diff --git a/x-pack/plugins/file_upload/server/capabilities.test.ts b/x-pack/plugins/file_upload/server/capabilities.test.ts index e5f0a01cd6abe0..b32150b80fe6b6 100644 --- a/x-pack/plugins/file_upload/server/capabilities.test.ts +++ b/x-pack/plugins/file_upload/server/capabilities.test.ts @@ -26,6 +26,16 @@ describe('setupCapabilities', () => { `); }); + it('registers a capabilities switcher with the correct options', async () => { + const coreSetup = coreMock.createSetup(); + setupCapabilities(coreSetup); + + expect(coreSetup.capabilities.registerSwitcher).toHaveBeenCalledTimes(1); + expect(coreSetup.capabilities.registerSwitcher).toHaveBeenCalledWith(expect.any(Function), { + capabilityPath: 'fileUpload.*', + }); + }); + it('registers a capabilities switcher that returns unaltered capabilities when security is disabled', async () => { const coreSetup = coreMock.createSetup(); setupCapabilities(coreSetup); diff --git a/x-pack/plugins/file_upload/server/capabilities.ts b/x-pack/plugins/file_upload/server/capabilities.ts index 9217176fb2b255..9e940a0e9a6492 100644 --- a/x-pack/plugins/file_upload/server/capabilities.ts +++ b/x-pack/plugins/file_upload/server/capabilities.ts @@ -20,28 +20,33 @@ export const setupCapabilities = ( }; }); - core.capabilities.registerSwitcher(async (request, capabilities, useDefaultCapabilities) => { - if (useDefaultCapabilities) { - return {}; - } - const [, { security }] = await core.getStartServices(); + core.capabilities.registerSwitcher( + async (request, capabilities, useDefaultCapabilities) => { + if (useDefaultCapabilities) { + return {}; + } + const [, { security }] = await core.getStartServices(); - // Check the bare minimum set of privileges required to get some utility out of this feature - const { hasImportPermission } = await checkFileUploadPrivileges({ - authorization: security?.authz, - request, - checkCreateDataView: true, - checkHasManagePipeline: false, - }); + // Check the bare minimum set of privileges required to get some utility out of this feature + const { hasImportPermission } = await checkFileUploadPrivileges({ + authorization: security?.authz, + request, + checkCreateDataView: true, + checkHasManagePipeline: false, + }); - if (!hasImportPermission) { - return { - fileUpload: { - show: false, - }, - }; - } + if (!hasImportPermission) { + return { + fileUpload: { + show: false, + }, + }; + } - return {}; - }); + return {}; + }, + { + capabilityPath: 'fileUpload.*', + } + ); }; diff --git a/x-pack/plugins/fleet/common/openapi/bundled.json b/x-pack/plugins/fleet/common/openapi/bundled.json index 5953bd1fdc2871..e0b754430521d0 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.json +++ b/x-pack/plugins/fleet/common/openapi/bundled.json @@ -2790,14 +2790,6 @@ "schema": { "type": "object", "properties": { - "revoke": { - "type": "boolean", - "description": "Revokes API keys of agents" - }, - "force": { - "type": "boolean", - "description": "Unenroll hosted agents too" - }, "agents": { "oneOf": [ { @@ -2812,6 +2804,18 @@ "description": "list of agent IDs" } ] + }, + "revoke": { + "type": "boolean", + "description": "Revokes API keys of agents" + }, + "force": { + "type": "boolean", + "description": "Unenrolls hosted agents too" + }, + "includeInactive": { + "type": "boolean", + "description": "When passing agents by KQL query, unenrolls inactive agents too" } }, "required": [ diff --git a/x-pack/plugins/fleet/common/openapi/bundled.yaml b/x-pack/plugins/fleet/common/openapi/bundled.yaml index c348cec266c124..d977af4f9c2b57 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.yaml +++ b/x-pack/plugins/fleet/common/openapi/bundled.yaml @@ -1750,12 +1750,6 @@ paths: schema: type: object properties: - revoke: - type: boolean - description: Revokes API keys of agents - force: - type: boolean - description: Unenroll hosted agents too agents: oneOf: - type: string @@ -1764,6 +1758,17 @@ paths: items: type: string description: list of agent IDs + revoke: + type: boolean + description: Revokes API keys of agents + force: + type: boolean + description: Unenrolls hosted agents too + includeInactive: + type: boolean + description: >- + When passing agents by KQL query, unenrolls inactive agents + too required: - agents example: diff --git a/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_unenroll.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_unenroll.yaml index 1ab9e4038b978c..a765e4868442bb 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_unenroll.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_unenroll.yaml @@ -23,12 +23,6 @@ post: schema: type: object properties: - revoke: - type: boolean - description: Revokes API keys of agents - force: - type: boolean - description: Unenroll hosted agents too agents: oneOf: - type: string @@ -37,6 +31,15 @@ post: items: type: string description: list of agent IDs + revoke: + type: boolean + description: Revokes API keys of agents + force: + type: boolean + description: Unenrolls hosted agents too + includeInactive: + type: boolean + description: When passing agents by KQL query, unenrolls inactive agents too required: - agents example: diff --git a/x-pack/plugins/fleet/common/services/index.ts b/x-pack/plugins/fleet/common/services/index.ts index 663cd27deab735..badaff8cb29d3b 100644 --- a/x-pack/plugins/fleet/common/services/index.ts +++ b/x-pack/plugins/fleet/common/services/index.ts @@ -7,6 +7,7 @@ export * from './routes'; export * as AgentStatusKueryHelper from './agent_status'; +export * from './package_helpers'; export { packageToPackagePolicyInputs, packageToPackagePolicy, diff --git a/x-pack/plugins/fleet/common/services/package_helpers.test.ts b/x-pack/plugins/fleet/common/services/package_helpers.test.ts new file mode 100644 index 00000000000000..71d3a9018ed5e0 --- /dev/null +++ b/x-pack/plugins/fleet/common/services/package_helpers.test.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { isRootPrivilegesRequired } from './package_helpers'; + +describe('isRootPrivilegesRequired', () => { + it('should return true if root privileges is required at root level', () => { + const res = isRootPrivilegesRequired({ + agent: { + privileges: { + root: true, + }, + }, + } as any); + expect(res).toBe(true); + }); + it('should return true if root privileges is required at datastreams', () => { + const res = isRootPrivilegesRequired({ + data_streams: [ + { + agent: { + privileges: { root: true }, + }, + }, + ], + } as any); + expect(res).toBe(true); + }); + + it('should return false if root privileges is not required', () => { + const res = isRootPrivilegesRequired({ + data_streams: [], + } as any); + expect(res).toBe(false); + }); +}); diff --git a/x-pack/plugins/fleet/common/services/package_helpers.ts b/x-pack/plugins/fleet/common/services/package_helpers.ts new file mode 100644 index 00000000000000..0282d218cec39b --- /dev/null +++ b/x-pack/plugins/fleet/common/services/package_helpers.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { PackageInfo } from '../types'; + +/** + * Return true if a package need Elastic Agent to be run as root/administrator + */ +export function isRootPrivilegesRequired(packageInfo: PackageInfo) { + return ( + packageInfo.agent?.privileges?.root || + packageInfo.data_streams?.some((d) => d.agent?.privileges?.root) + ); +} diff --git a/x-pack/plugins/fleet/common/types/models/epm.ts b/x-pack/plugins/fleet/common/types/models/epm.ts index d8400a8a61b9bb..c4d4e9c7637663 100644 --- a/x-pack/plugins/fleet/common/types/models/epm.ts +++ b/x-pack/plugins/fleet/common/types/models/epm.ts @@ -337,6 +337,7 @@ export enum RegistryDataStreamKeys { dataset_is_prefix = 'dataset_is_prefix', routing_rules = 'routing_rules', lifecycle = 'lifecycle', + agent = 'agent', } export interface RegistryDataStream { @@ -355,6 +356,12 @@ export interface RegistryDataStream { [RegistryDataStreamKeys.dataset_is_prefix]?: boolean; [RegistryDataStreamKeys.routing_rules]?: RegistryDataStreamRoutingRules[]; [RegistryDataStreamKeys.lifecycle]?: RegistryDataStreamLifecycle; + [RegistryDataStreamKeys.lifecycle]?: RegistryDataStreamLifecycle; + [RegistryDataStreamKeys.agent]?: RegistryAgent; +} + +export interface RegistryAgent { + privileges?: { root?: boolean }; } export interface RegistryElasticsearch { diff --git a/x-pack/plugins/fleet/common/types/models/package_spec.ts b/x-pack/plugins/fleet/common/types/models/package_spec.ts index d372defd72c0ef..60bc051af04fc9 100644 --- a/x-pack/plugins/fleet/common/types/models/package_spec.ts +++ b/x-pack/plugins/fleet/common/types/models/package_spec.ts @@ -31,6 +31,9 @@ export interface PackageSpecManifest { RegistryElasticsearch, 'index_template.settings' | 'index_template.mappings' | 'index_template.data_stream' >; + agent?: { + privileges?: { root?: boolean }; + }; asset_tags?: PackageSpecTags[]; } export interface PackageSpecTags { diff --git a/x-pack/plugins/fleet/common/types/rest_spec/agent.ts b/x-pack/plugins/fleet/common/types/rest_spec/agent.ts index 1e0e82d6c2ecdf..edb3f7f0eefcba 100644 --- a/x-pack/plugins/fleet/common/types/rest_spec/agent.ts +++ b/x-pack/plugins/fleet/common/types/rest_spec/agent.ts @@ -85,6 +85,7 @@ export interface PostBulkAgentUnenrollRequest { agents: string[] | string; force?: boolean; revoke?: boolean; + includeInactive?: boolean; }; } diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_yaml_flyout.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_yaml_flyout.tsx index d1cfd995cdda91..6488550b18358e 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_yaml_flyout.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_yaml_flyout.tsx @@ -27,6 +27,7 @@ import { import { useGetOneAgentPolicyFull, useGetOneAgentPolicy, useStartServices } from '../../../hooks'; import { Loading } from '../../../components'; import { fullAgentPolicyToYaml, agentPolicyRouteService } from '../../../services'; +import { API_VERSIONS } from '../../../../../../common/constants'; const FlyoutBody = styled(EuiFlyoutBody)` .euiFlyoutBody__overflowContent { @@ -65,9 +66,9 @@ export const AgentPolicyYamlFlyout = memo<{ policyId: string; onClose: () => voi ); - const downloadLink = core.http.basePath.prepend( - agentPolicyRouteService.getInfoFullDownloadPath(policyId) - ); + const downloadLink = + core.http.basePath.prepend(agentPolicyRouteService.getInfoFullDownloadPath(policyId)) + + `?apiVersion=${API_VERSIONS.public.v1}`; return ( diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/page_steps/install_agent/install_agent_standalone.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/page_steps/install_agent/install_agent_standalone.tsx index 3f84a95239beb2..f49aaefd65dd9b 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/page_steps/install_agent/install_agent_standalone.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/page_steps/install_agent/install_agent_standalone.tsx @@ -11,7 +11,7 @@ import { EuiSteps, EuiSpacer } from '@elastic/eui'; import { safeDump } from 'js-yaml'; import type { FullAgentPolicy } from '../../../../../../../../../../common/types/models/agent_policy'; - +import { API_VERSIONS } from '../../../../../../../../../../common/constants'; import { AgentStandaloneBottomBar, StandaloneModeWarningCallout, @@ -95,7 +95,9 @@ export const InstallElasticAgentStandalonePageStep: React.FC )} + {packageInfo && isRootPrivilegesRequired(packageInfo) ? ( + <> + + } + > + + + + + ) : null} {numTransformAssets > 0 ? ( <> diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/action_menu.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/action_menu.test.tsx index 2f05354b09a040..2d39700da5caaf 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/action_menu.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/action_menu.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { fireEvent } from '@testing-library/dom'; +import { fireEvent } from '@testing-library/react'; import { createFleetTestRendererMock } from '../../../../../../mock'; import type { Agent, AgentPolicy } from '../../../../types'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_activity_flyout.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_activity_flyout.tsx index 2c1def34611184..d23358726c11ea 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_activity_flyout.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_activity_flyout.tsx @@ -26,6 +26,7 @@ import { EuiButtonEmpty, EuiFlyoutFooter, EuiSpacer, + EuiToolTip, } from '@elastic/eui'; import styled from 'styled-components'; @@ -57,6 +58,8 @@ const FlyoutFooterWPadding = styled(EuiFlyoutFooter)` padding: 16px 24px !important; `; +const MAX_VIEW_AGENTS_COUNT = 2000; + export const AgentActivityFlyout: React.FunctionComponent<{ onClose: () => void; onAbortSuccess: () => void; @@ -702,17 +705,42 @@ const ViewAgentsButton: React.FunctionComponent<{ action: ActionStatus; onClickViewAgents: (action: ActionStatus) => void; }> = ({ action, onClickViewAgents }) => { - return action.type !== 'UPDATE_TAGS' ? ( + if (action.type === 'UPDATE_TAGS') { + return null; + } + + const button = ( onClickViewAgents(action)} flush="left" data-test-subj="agentActivityFlyout.viewAgentsButton" + disabled={action.nbAgentsActionCreated > MAX_VIEW_AGENTS_COUNT} > - ) : null; + ); + + if (action.nbAgentsActionCreated <= MAX_VIEW_AGENTS_COUNT) { + return button; + } + + return ( + + } + > + {button} + + ); }; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_upgrade_status.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_upgrade_status.test.tsx index 9937126213c918..dffa4bc665bdb4 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_upgrade_status.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_upgrade_status.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { fireEvent, waitFor } from '@testing-library/dom'; +import { fireEvent, waitFor } from '@testing-library/react'; import React from 'react'; import { createFleetTestRendererMock } from '../../../../../../mock'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/table_row_actions.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/table_row_actions.test.tsx index a2fed8d3ed6133..4e8d25f313ea5c 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/table_row_actions.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/table_row_actions.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { fireEvent } from '@testing-library/dom'; +import { fireEvent } from '@testing-library/react'; import { createFleetTestRendererMock } from '../../../../../../mock'; import type { Agent, AgentPolicy } from '../../../../types'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_unenroll_modal/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_unenroll_modal/index.tsx index 6fac687107fd1a..6d32a8afeb769b 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_unenroll_modal/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_unenroll_modal/index.tsx @@ -47,6 +47,8 @@ export const AgentUnenrollAgentModal: React.FunctionComponent = ({ : await sendPostBulkAgentUnenroll({ agents: Array.isArray(agents) ? agents.map((agent) => agent.id) : agents, revoke: forceUnenroll, + // includeInactive is only used when the agents are selected by query, it's ignored in the case of agent ids + includeInactive: true, }); if (error) { throw error; diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/overview.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/overview.tsx index cd8fbebe7f2a39..517c0552f925b1 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/overview.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/overview.tsx @@ -23,6 +23,7 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { isIntegrationPolicyTemplate, isPackagePrerelease, + isRootPrivilegesRequired, } from '../../../../../../../../common/services'; import { @@ -37,6 +38,7 @@ import type { PackageInfo, RegistryPolicyTemplate } from '../../../../../types'; import { Screenshots } from './screenshots'; import { Readme } from './readme'; import { Details } from './details'; +import { Requirements } from './requirements'; interface Props { packageInfo: PackageInfo; @@ -277,6 +279,8 @@ export const OverviewPage: React.FC = memo( ]; }, [h1, navItems]); + const requireAgentRootPrivileges = isRootPrivilegesRequired(packageInfo); + return ( @@ -309,6 +313,11 @@ export const OverviewPage: React.FC = memo( + {requireAgentRootPrivileges ? ( + + + + ) : null} {screenshots.length ? ( { + return ( + + + + + +

    + +

    +
    +
    +
    +
    + + + + + + + + ), + }, + ]} + /> + +
    + ); +}); diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/kubernetes_instructions.test.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/kubernetes_instructions.test.tsx index 9eab4d3e0c99a8..c2bfd10ef6f7f3 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/kubernetes_instructions.test.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/kubernetes_instructions.test.tsx @@ -10,13 +10,13 @@ import { getManifestDownloadLink } from './kubernetes_instructions'; describe('getManifestDownloadLink', () => { it('should return the correct link', () => { expect(getManifestDownloadLink('https://fleet.host', 'enrollmentToken')).toEqual( - '/api/fleet/kubernetes/download?fleetServer=https%3A%2F%2Ffleet.host&enrolToken=enrollmentToken' + '/api/fleet/kubernetes/download?apiVersion=2023-10-31&fleetServer=https%3A%2F%2Ffleet.host&enrolToken=enrollmentToken' ); expect(getManifestDownloadLink('https://fleet.host')).toEqual( - '/api/fleet/kubernetes/download?fleetServer=https%3A%2F%2Ffleet.host' + '/api/fleet/kubernetes/download?apiVersion=2023-10-31&fleetServer=https%3A%2F%2Ffleet.host' ); expect(getManifestDownloadLink(undefined, 'enrollmentToken')).toEqual( - '/api/fleet/kubernetes/download?enrolToken=enrollmentToken' + '/api/fleet/kubernetes/download?apiVersion=2023-10-31&enrolToken=enrollmentToken' ); }); }); diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/kubernetes_instructions.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/kubernetes_instructions.tsx index a44b7ab4020e9a..f1cf6b5a29032c 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/kubernetes_instructions.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/kubernetes_instructions.tsx @@ -20,7 +20,7 @@ import { i18n } from '@kbn/i18n'; import { useStartServices } from '../../hooks'; -import { agentPolicyRouteService } from '../../../common'; +import { agentPolicyRouteService, API_VERSIONS } from '../../../common'; import { sendGetK8sManifest } from '../../hooks/use_request/k8s'; @@ -33,6 +33,7 @@ interface Props { export const getManifestDownloadLink = (fleetServerHost?: string, enrollmentAPIKey?: string) => { const searchParams = new URLSearchParams({ + apiVersion: API_VERSIONS.public.v1, ...(fleetServerHost && { fleetServer: fleetServerHost }), ...(enrollmentAPIKey && { enrolToken: enrollmentAPIKey }), }); diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/compute_steps.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/compute_steps.tsx index 495266897db894..5cc8c109131aef 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/compute_steps.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/compute_steps.tsx @@ -13,7 +13,7 @@ import { safeDump } from 'js-yaml'; import type { EuiContainedStepProps } from '@elastic/eui/src/components/steps/steps'; import type { FullAgentPolicy } from '../../../../common/types/models/agent_policy'; - +import { API_VERSIONS } from '../../../../common/constants'; import { fullAgentPolicyToYaml, agentPolicyRouteService, @@ -73,10 +73,12 @@ export const StandaloneSteps: React.FunctionComponent = ({ ? core.http.basePath.prepend( `${agentPolicyRouteService.getInfoFullDownloadPath( selectedPolicy?.id - )}?kubernetes=true&standalone=true` + )}?kubernetes=true&standalone=true&apiVersion=${API_VERSIONS.public.v1}` ) : core.http.basePath.prepend( - `${agentPolicyRouteService.getInfoFullDownloadPath(selectedPolicy?.id)}?standalone=true` + `${agentPolicyRouteService.getInfoFullDownloadPath( + selectedPolicy?.id + )}?standalone=true&apiVersion=${API_VERSIONS.public.v1}` ); } diff --git a/x-pack/plugins/fleet/scripts/create_agents/create_agents.ts b/x-pack/plugins/fleet/scripts/create_agents/create_agents.ts index 52723d04503512..2c510a2ca3ffe5 100644 --- a/x-pack/plugins/fleet/scripts/create_agents/create_agents.ts +++ b/x-pack/plugins/fleet/scripts/create_agents/create_agents.ts @@ -43,7 +43,7 @@ const ES_PASSWORD = 'password'; const DEFAULT_AGENT_COUNT = 50000; -const INDEX_BULK_OP = '{ "index":{ } }\n'; +const INDEX_BULK_OP = '{ "index":{ "_id": "{{id}}" } }\n'; const { delete: deleteAgentsFirst = false, @@ -145,6 +145,10 @@ function createAgentWithStatus({ hostname: string; }) { const baseAgent = { + agent: { + id: uuidv4(), + version, + }, access_api_key_id: 'api-key-1', active: true, policy_id: policyId, @@ -235,7 +239,12 @@ async function deleteAgents() { async function createAgentDocsBulk(agents: Agent[]) { const auth = 'Basic ' + Buffer.from(ES_SUPERUSER + ':' + ES_PASSWORD).toString('base64'); - const body = agents.flatMap((agent) => [INDEX_BULK_OP, JSON.stringify(agent) + '\n']).join(''); + const body = agents + .flatMap((agent) => [ + INDEX_BULK_OP.replace(/{{id}}/, agent.agent?.id ?? ''), + JSON.stringify(agent) + '\n', + ]) + .join(''); const res = await fetch(`${ES_URL}/.fleet-agents/_bulk`, { method: 'post', body, diff --git a/x-pack/plugins/fleet/server/routes/agent/handlers.ts b/x-pack/plugins/fleet/server/routes/agent/handlers.ts index 2f344b0f3fb15c..81a01de6b5fde9 100644 --- a/x-pack/plugins/fleet/server/routes/agent/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/agent/handlers.ts @@ -354,8 +354,9 @@ function isStringArray(arr: unknown | string[]): arr is string[] { export const getAvailableVersionsHandler: RequestHandler = async (context, request, response) => { try { - const availableVersions = await AgentService.getAvailableVersions({}); + const availableVersions = await AgentService.getAvailableVersions(); const body: GetAvailableVersionsResponse = { items: availableVersions }; + return response.ok({ body }); } catch (error) { return defaultFleetErrorHandler({ error, response }); diff --git a/x-pack/plugins/fleet/server/routes/agent/unenroll_handler.ts b/x-pack/plugins/fleet/server/routes/agent/unenroll_handler.ts index 740cf3a7b716ce..ec72f23a9876dc 100644 --- a/x-pack/plugins/fleet/server/routes/agent/unenroll_handler.ts +++ b/x-pack/plugins/fleet/server/routes/agent/unenroll_handler.ts @@ -55,6 +55,7 @@ export const postBulkAgentsUnenrollHandler: RequestHandler< revoke: request.body?.revoke, force: request.body?.force, batchSize: request.body?.batchSize, + showInactive: request.body?.includeInactive, }); return response.ok({ body: { actionId: results.actionId } }); diff --git a/x-pack/plugins/fleet/server/routes/agent_policy/index.ts b/x-pack/plugins/fleet/server/routes/agent_policy/index.ts index 7ca3409f72b887..747bc6face1f8c 100644 --- a/x-pack/plugins/fleet/server/routes/agent_policy/index.ts +++ b/x-pack/plugins/fleet/server/routes/agent_policy/index.ts @@ -174,6 +174,7 @@ export const registerRoutes = (router: FleetAuthzRouter) => { fleetAuthz: { fleet: { all: true }, }, + enableQueryVersion: true, }) .addVersion( { @@ -206,6 +207,7 @@ export const registerRoutes = (router: FleetAuthzRouter) => { fleetAuthz: { fleet: { all: true }, }, + enableQueryVersion: true, }) .addVersion( { diff --git a/x-pack/plugins/fleet/server/saved_objects/index.ts b/x-pack/plugins/fleet/server/saved_objects/index.ts index 7c68ab9105e492..b87310e850c81f 100644 --- a/x-pack/plugins/fleet/server/saved_objects/index.ts +++ b/x-pack/plugins/fleet/server/saved_objects/index.ts @@ -25,6 +25,8 @@ import { UNINSTALL_TOKENS_SAVED_OBJECT_TYPE, } from '../constants'; +import { migrateSyntheticsPackagePolicyToV8120 } from './migrations/synthetics/to_v8_12_0'; + import { migratePackagePolicyEvictionsFromV8110, migratePackagePolicyToV8110, @@ -394,6 +396,14 @@ const getSavedObjectTypes = (): { [key: string]: SavedObjectsType } => ({ }, ], }, + '5': { + changes: [ + { + type: 'data_backfill', + backfillFn: migrateSyntheticsPackagePolicyToV8120, + }, + ], + }, }, migrations: { '7.10.0': migratePackagePolicyToV7100, diff --git a/x-pack/plugins/fleet/server/saved_objects/migrations/synthetics/to_v8_12_0.test.ts b/x-pack/plugins/fleet/server/saved_objects/migrations/synthetics/to_v8_12_0.test.ts new file mode 100644 index 00000000000000..755897c50ef961 --- /dev/null +++ b/x-pack/plugins/fleet/server/saved_objects/migrations/synthetics/to_v8_12_0.test.ts @@ -0,0 +1,146 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { SavedObjectModelTransformationContext } from '@kbn/core-saved-objects-server'; + +import type { PackagePolicy } from '../../../../common'; + +import { getBrowserPolicy } from './fixtures/8.7.0'; + +import { migrateSyntheticsPackagePolicyToV8120 as migration } from './to_v8_12_0'; +import { migrateSyntheticsPackagePolicyToV8100 as migration10 } from './to_v8_10_0'; + +describe('8.12.0 Synthetics Package Policy migration', () => { + describe('processors migration', () => { + it('handles processors field for empty values', () => { + const doc = getBrowserPolicy('false'); + const actual10 = migration10(doc, {} as SavedObjectModelTransformationContext); + doc.attributes = actual10.attributes as PackagePolicy; + + const actual = migration( + { ...doc, namespace: 'default' }, + {} as SavedObjectModelTransformationContext + ); + expect(actual.attributes?.inputs?.[3]?.streams[0]?.vars?.processors?.value).toEqual( + JSON.stringify([ + { + add_fields: { + fields: { + 'monitor.fleet_managed': true, + config_id: '420754e9-40f2-486c-bc2e-265bafd735c5', + meta: { space_id: 'default' }, + }, + target: '', + }, + }, + ]) + ); + expect(actual.attributes?.inputs?.[3]?.streams[0]?.compiled_stream?.processors).toEqual( + JSON.stringify([ + { + add_fields: { + fields: { + 'monitor.fleet_managed': true, + config_id: '420754e9-40f2-486c-bc2e-265bafd735c5', + meta: { space_id: 'default' }, + }, + target: '', + }, + }, + ]) + ); + }); + + it('handles processors field for project monitor', () => { + const doc = getBrowserPolicy('', 'test-project'); + const actual10 = migration10(doc, {} as SavedObjectModelTransformationContext); + doc.attributes = actual10.attributes as PackagePolicy; + const actual = migration( + { ...doc, namespace: 'default' }, + {} as SavedObjectModelTransformationContext + ); + expect(actual.attributes?.inputs?.[3]?.streams[0]?.vars?.processors?.value).toEqual( + JSON.stringify([ + { + add_fields: { + fields: { + 'monitor.fleet_managed': true, + config_id: '420754e9-40f2-486c-bc2e-265bafd735c5', + 'monitor.project.name': 'test-project', + 'monitor.project.id': 'test-project', + meta: { space_id: 'default' }, + }, + target: '', + }, + }, + ]) + ); + expect(actual.attributes?.inputs?.[3]?.streams[0]?.compiled_stream.processors).toEqual( + JSON.stringify([ + { + add_fields: { + fields: { + 'monitor.fleet_managed': true, + config_id: '420754e9-40f2-486c-bc2e-265bafd735c5', + 'monitor.project.name': 'test-project', + 'monitor.project.id': 'test-project', + meta: { space_id: 'default' }, + }, + target: '', + }, + }, + ]) + ); + }); + + it('handles processors field for test now fields', () => { + const doc = getBrowserPolicy('', 'test-project', 'test-run-id', true); + const actual10 = migration10(doc, {} as SavedObjectModelTransformationContext); + doc.attributes = actual10.attributes as PackagePolicy; + const actual = migration( + { ...doc, namespace: 'test' }, + {} as SavedObjectModelTransformationContext + ); + expect(actual.attributes?.inputs?.[3]?.streams[0]?.vars?.processors?.value).toEqual( + JSON.stringify([ + { + add_fields: { + fields: { + 'monitor.fleet_managed': true, + test_run_id: 'test-run-id', + run_once: true, + config_id: '420754e9-40f2-486c-bc2e-265bafd735c5', + 'monitor.project.name': 'test-project', + 'monitor.project.id': 'test-project', + meta: { space_id: 'test' }, + }, + target: '', + }, + }, + ]) + ); + expect(actual.attributes?.inputs?.[3]?.streams[0]?.compiled_stream.processors).toEqual( + JSON.stringify([ + { + add_fields: { + fields: { + 'monitor.fleet_managed': true, + test_run_id: 'test-run-id', + run_once: true, + config_id: '420754e9-40f2-486c-bc2e-265bafd735c5', + 'monitor.project.name': 'test-project', + 'monitor.project.id': 'test-project', + meta: { space_id: 'test' }, + }, + target: '', + }, + }, + ]) + ); + }); + }); +}); diff --git a/x-pack/plugins/fleet/server/saved_objects/migrations/synthetics/to_v8_12_0.ts b/x-pack/plugins/fleet/server/saved_objects/migrations/synthetics/to_v8_12_0.ts new file mode 100644 index 00000000000000..c7acea2e5d4a9d --- /dev/null +++ b/x-pack/plugins/fleet/server/saved_objects/migrations/synthetics/to_v8_12_0.ts @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { SavedObjectModelDataBackfillFn } from '@kbn/core-saved-objects-server'; + +import type { PackagePolicy } from '../../../../common'; + +export const migrateSyntheticsPackagePolicyToV8120: SavedObjectModelDataBackfillFn< + PackagePolicy, + PackagePolicy +> = (packagePolicyDoc) => { + if ( + packagePolicyDoc.attributes.package?.name !== 'synthetics' || + !packagePolicyDoc.attributes.is_managed + ) { + return packagePolicyDoc; + } + const updatedAttributes = packagePolicyDoc.attributes; + const namespace = packagePolicyDoc.namespace; + + const enabledInput = updatedAttributes.inputs.find((input) => input.enabled === true); + const enabledStream = enabledInput?.streams.find((stream) => { + return ['browser', 'http', 'icmp', 'tcp'].includes(stream.data_stream.dataset); + }); + if (!enabledStream) { + return { + attributes: updatedAttributes, + }; + } + + if (enabledStream.vars) { + const processors = processorsFormatter(enabledStream.vars.processors.value, namespace); + enabledStream.vars.processors = { value: processors, type: 'yaml' }; + enabledStream.compiled_stream.processors = processors; + } + + return { + attributes: updatedAttributes, + }; +}; + +export const processorsFormatter = (processorsStr: string, namespace?: string) => { + try { + const processors = JSON.parse(processorsStr); + processors[0].add_fields.fields.meta = { space_id: namespace }; + return JSON.stringify(processors); + } catch (e) { + return processorsStr; + } +}; diff --git a/x-pack/plugins/fleet/server/services/agents/crud.test.ts b/x-pack/plugins/fleet/server/services/agents/crud.test.ts index 47bb8028fffcdd..e542881fe13f3b 100644 --- a/x-pack/plugins/fleet/server/services/agents/crud.test.ts +++ b/x-pack/plugins/fleet/server/services/agents/crud.test.ts @@ -9,7 +9,9 @@ import type { ElasticsearchClient } from '@kbn/core/server'; import { elasticsearchServiceMock, savedObjectsClientMock } from '@kbn/core/server/mocks'; import { AGENTS_INDEX } from '../../constants'; +import { createAppContextStartContractMock } from '../../mocks'; import type { Agent } from '../../types'; +import { appContextService } from '../app_context'; import { auditLoggingService } from '../audit_logging'; @@ -30,6 +32,7 @@ const mockedAuditLoggingService = auditLoggingService as jest.Mocked { const soClientMock = savedObjectsClientMock.create(); + let mockContract: ReturnType; let esClientMock: ElasticsearchClient; let searchMock: jest.Mock; @@ -41,6 +44,9 @@ describe('Agents CRUD test', () => { openPointInTime: jest.fn().mockResolvedValue({ id: '1' }), closePointInTime: jest.fn(), } as unknown as ElasticsearchClient; + + mockContract = createAppContextStartContractMock(); + appContextService.start(mockContract); }); function getEsResponse(ids: string[], total: number) { diff --git a/x-pack/plugins/fleet/server/services/agents/unenroll.ts b/x-pack/plugins/fleet/server/services/agents/unenroll.ts index f13a8f91d81e93..e2c2a17c4d2ad4 100644 --- a/x-pack/plugins/fleet/server/services/agents/unenroll.ts +++ b/x-pack/plugins/fleet/server/services/agents/unenroll.ts @@ -73,6 +73,7 @@ export async function unenrollAgents( force?: boolean; revoke?: boolean; batchSize?: number; + showInactive?: boolean; } ): Promise<{ actionId: string }> { if ('agentIds' in options) { diff --git a/x-pack/plugins/fleet/server/services/agents/versions.test.ts b/x-pack/plugins/fleet/server/services/agents/versions.test.ts index 92e30141c006a8..513fba910705de 100644 --- a/x-pack/plugins/fleet/server/services/agents/versions.test.ts +++ b/x-pack/plugins/fleet/server/services/agents/versions.test.ts @@ -7,6 +7,8 @@ import { readFile } from 'fs/promises'; +import fetch from 'node-fetch'; + let mockKibanaVersion = '300.0.0'; let mockConfig = {}; jest.mock('../app_context', () => { @@ -21,16 +23,30 @@ jest.mock('../app_context', () => { }); jest.mock('fs/promises'); +jest.mock('node-fetch'); const mockedReadFile = readFile as jest.MockedFunction; +const mockedFetch = fetch as jest.MockedFunction; + +const emptyResponse = { + status: 200, + text: jest.fn().mockResolvedValue(JSON.stringify({})), +} as any; + import { getAvailableVersions } from './versions'; describe('getAvailableVersions', () => { + beforeEach(() => { + mockedReadFile.mockReset(); + mockedFetch.mockReset(); + }); + it('should return available version and filter version < 7.17', async () => { mockKibanaVersion = '300.0.0'; mockedReadFile.mockResolvedValue(`["8.1.0", "8.0.0", "7.17.0", "7.16.0"]`); + mockedFetch.mockResolvedValueOnce(emptyResponse); - const res = await getAvailableVersions({ cached: false, includeCurrentVersion: true }); + const res = await getAvailableVersions({ includeCurrentVersion: true, ignoreCache: true }); expect(res).toEqual(['300.0.0', '8.1.0', '8.0.0', '7.17.0']); }); @@ -38,8 +54,9 @@ describe('getAvailableVersions', () => { it('should not strip -SNAPSHOT from kibana version', async () => { mockKibanaVersion = '300.0.0-SNAPSHOT'; mockedReadFile.mockResolvedValue(`["8.1.0", "8.0.0", "7.17.0", "7.16.0"]`); + mockedFetch.mockResolvedValueOnce(emptyResponse); - const res = await getAvailableVersions({ cached: false, includeCurrentVersion: true }); + const res = await getAvailableVersions({ includeCurrentVersion: true, ignoreCache: true }); expect(res).toEqual(['300.0.0-SNAPSHOT', '8.1.0', '8.0.0', '7.17.0']); }); @@ -51,8 +68,9 @@ describe('getAvailableVersions', () => { }, }; mockedReadFile.mockResolvedValue(`["8.1.0", "8.0.0", "7.17.0", "7.16.0"]`); + mockedFetch.mockResolvedValueOnce(emptyResponse); - const res = await getAvailableVersions({ cached: false }); + const res = await getAvailableVersions({ ignoreCache: true }); expect(res).toEqual(['8.1.0', '8.0.0', '7.17.0']); }); @@ -60,8 +78,9 @@ describe('getAvailableVersions', () => { it('should not include the current version if includeCurrentVersion = false', async () => { mockKibanaVersion = '300.0.0-SNAPSHOT'; mockedReadFile.mockResolvedValue(`["8.1.0", "8.0.0", "7.17.0", "7.16.0"]`); + mockedFetch.mockResolvedValueOnce(emptyResponse); - const res = await getAvailableVersions({ cached: false, includeCurrentVersion: false }); + const res = await getAvailableVersions({ includeCurrentVersion: false, ignoreCache: true }); expect(res).toEqual(['8.1.0', '8.0.0', '7.17.0']); }); @@ -74,9 +93,90 @@ describe('getAvailableVersions', () => { }, }; mockedReadFile.mockRejectedValue({ code: 'ENOENT' }); + mockedFetch.mockResolvedValueOnce(emptyResponse); - const res = await getAvailableVersions({ cached: false }); + const res = await getAvailableVersions({ ignoreCache: true }); expect(res).toEqual(['300.0.0']); }); + + it('should include versions returned from product_versions API', async () => { + mockKibanaVersion = '300.0.0'; + mockedReadFile.mockResolvedValue(`["8.1.0", "8.0.0", "7.17.0", "7.16.0"]`); + mockedFetch.mockResolvedValueOnce({ + status: 200, + text: jest.fn().mockResolvedValue( + JSON.stringify([ + [ + { + title: 'Elastic Agent 8.1.0', + version_number: '8.1.0', + }, + { + title: 'Elastic Agent 8.10.0', + version_number: '8.10.0', + }, + { + title: 'Elastic Agent 8.9.2', + version_number: '8.9.2', + }, + , + ], + ]) + ), + } as any); + + const res = await getAvailableVersions({ ignoreCache: true }); + + // Should sort, uniquify and filter out versions < 7.17 + expect(res).toEqual(['8.10.0', '8.9.2', '8.1.0', '8.0.0', '7.17.0']); + }); + + it('should cache results', async () => { + mockKibanaVersion = '300.0.0'; + mockedReadFile.mockResolvedValue(`["8.1.0", "8.0.0", "7.17.0", "7.16.0"]`); + mockedFetch.mockResolvedValueOnce({ + status: 200, + text: jest.fn().mockResolvedValue( + JSON.stringify([ + [ + { + title: 'Elastic Agent 8.1.0', + version_number: '8.1.0', + }, + { + title: 'Elastic Agent 8.10.0', + version_number: '8.10.0', + }, + { + title: 'Elastic Agent 8.9.2', + version_number: '8.9.2', + }, + , + ], + ]) + ), + } as any); + + await getAvailableVersions(); + + mockedFetch.mockResolvedValueOnce({ + status: 200, + text: jest.fn().mockResolvedValue( + JSON.stringify([ + [ + { + title: 'Elastic Agent 300.0.0', + version_number: '300.0.0', + }, + ], + ]) + ), + } as any); + + const res2 = await getAvailableVersions(); + + expect(mockedFetch).toBeCalledTimes(1); + expect(res2).not.toContain('300.0.0'); + }); }); diff --git a/x-pack/plugins/fleet/server/services/agents/versions.ts b/x-pack/plugins/fleet/server/services/agents/versions.ts index 7a1b82bb723594..8f31d3f12b3443 100644 --- a/x-pack/plugins/fleet/server/services/agents/versions.ts +++ b/x-pack/plugins/fleet/server/services/agents/versions.ts @@ -8,18 +8,27 @@ import { readFile } from 'fs/promises'; import Path from 'path'; -import { REPO_ROOT } from '@kbn/repo-info'; +import fetch from 'node-fetch'; +import pRetry from 'p-retry'; import { uniq } from 'lodash'; import semverGte from 'semver/functions/gte'; import semverGt from 'semver/functions/gt'; import semverCoerce from 'semver/functions/coerce'; +import { REPO_ROOT } from '@kbn/repo-info'; + import { appContextService } from '..'; const MINIMUM_SUPPORTED_VERSION = '7.17.0'; const AGENT_VERSION_BUILD_FILE = 'x-pack/plugins/fleet/target/agent_versions_list.json'; -let availableVersions: string[] | undefined; +// Endpoint maintained by the web-team and hosted on the elastic website +const PRODUCT_VERSIONS_URL = 'https://www.elastic.co/api/product_versions'; + +// Cache available versions in memory for 1 hour +const CACHE_DURATION = 1000 * 60 * 60; +let CACHED_AVAILABLE_VERSIONS: string[] | undefined; +let LAST_FETCHED: number | undefined; export const getLatestAvailableVersion = async ( includeCurrentVersion?: boolean @@ -30,54 +39,100 @@ export const getLatestAvailableVersion = async ( }; export const getAvailableVersions = async ({ - cached = true, includeCurrentVersion, + ignoreCache = false, // This is only here to allow us to ignore the cache in tests }: { - cached?: boolean; includeCurrentVersion?: boolean; -}): Promise => { - // Use cached value to avoid reading from disk each time - if (cached && availableVersions) { - return availableVersions; + ignoreCache?: boolean; +} = {}): Promise => { + const logger = appContextService.getLogger(); + + if (LAST_FETCHED && !ignoreCache) { + const msSinceLastFetched = Date.now() - (LAST_FETCHED || 0); + + if (msSinceLastFetched < CACHE_DURATION && CACHED_AVAILABLE_VERSIONS !== undefined) { + logger.debug(`Cache is valid, returning cached available versions`); + + return CACHED_AVAILABLE_VERSIONS; + } + + logger.debug('Cache has expired, fetching available versions from disk + API'); } - // Read a static file generated at build time const config = appContextService.getConfig(); - let versionsToDisplay: string[] = []; - const kibanaVersion = appContextService.getKibanaVersion(); + let availableVersions: string[] = []; + + // First, grab available versions from the static file that's placed on disk at build time try { const file = await readFile(Path.join(REPO_ROOT, AGENT_VERSION_BUILD_FILE), 'utf-8'); - - // Exclude versions older than MINIMUM_SUPPORTED_VERSION and pre-release versions (SNAPSHOT, rc..) - // De-dup and sort in descending order const data: string[] = JSON.parse(file); - const versions = data - .map((item: any) => semverCoerce(item)?.version || '') - .filter((v: any) => semverGte(v, MINIMUM_SUPPORTED_VERSION)) - .sort((a: any, b: any) => (semverGt(a, b) ? -1 : 1)); - versionsToDisplay = uniq(versions) as string[]; + availableVersions = [...availableVersions, ...data]; + } catch (error) { + // If we can't read from the file, the error is non-blocking. We'll try to source data from the + // product versions API later. + logger.debug(`Error reading file ${AGENT_VERSION_BUILD_FILE}: ${error.message}`); + } + + // Next, fetch from the product versions API. This API call is aggressively cached, so we won't + // fetch from the live API more than `TIME_BETWEEN_FETCHES` milliseconds. + const apiVersions = await fetchAgentVersionsFromApi(); + + // Coerce each version to a semver object and compare to our `MINIMUM_SUPPORTED_VERSION` - we + // only want support versions in the final result. We'll also sort by newest version first. + availableVersions = uniq([...availableVersions, ...apiVersions]) + .map((item: any) => semverCoerce(item)?.version || '') + .filter((v: any) => semverGte(v, MINIMUM_SUPPORTED_VERSION)) + .sort((a: any, b: any) => (semverGt(a, b) ? -1 : 1)); + + // If the current stack version isn't included in the list of available versions, add it + // at the front of the array + const hasCurrentVersion = availableVersions.some((v) => v === kibanaVersion); + if (includeCurrentVersion && !hasCurrentVersion) { + availableVersions = [kibanaVersion, ...availableVersions]; + } - const appendCurrentVersion = includeCurrentVersion; + // Allow upgrading to the current stack version if this override flag is provided via `kibana.yml`. + // This is useful for development purposes. + if (availableVersions.length === 0 && !config?.internal?.onlyAllowAgentUpgradeToKnownVersions) { + availableVersions = [kibanaVersion]; + } - if (appendCurrentVersion) { - // Add current version if not already present - const hasCurrentVersion = versionsToDisplay.some((v) => v === kibanaVersion); + // Don't prime the cache in tests + if (!ignoreCache) { + CACHED_AVAILABLE_VERSIONS = availableVersions; + LAST_FETCHED = Date.now(); + } - versionsToDisplay = !hasCurrentVersion - ? [kibanaVersion].concat(versionsToDisplay) - : versionsToDisplay; - } + return availableVersions; +}; - availableVersions = versionsToDisplay; +async function fetchAgentVersionsFromApi() { + const logger = appContextService.getLogger(); - return availableVersions; - } catch (e) { - if (e.code === 'ENOENT') { - return config?.internal?.onlyAllowAgentUpgradeToKnownVersions ? [] : [kibanaVersion]; - } - throw e; + const options = { + headers: { + 'Content-Type': 'application/json', + }, + }; + + const response = await pRetry(() => fetch(PRODUCT_VERSIONS_URL, options), { retries: 1 }); + const rawBody = await response.text(); + + // We need to handle non-200 responses gracefully here to support airgapped environments where + // Kibana doesn't have internet access to query this API + if (response.status >= 400) { + logger.debug(`Status code ${response.status} received from versions API: ${rawBody}`); + return []; } -}; + + const jsonBody = JSON.parse(rawBody); + + const versions: string[] = (jsonBody.length ? jsonBody[0] : []) + .filter((item: any) => item?.title?.includes('Elastic Agent')) + .map((item: any) => item?.version_number); + + return versions; +} diff --git a/x-pack/plugins/fleet/server/services/epm/kibana/assets/tag_assets.test.ts b/x-pack/plugins/fleet/server/services/epm/kibana/assets/tag_assets.test.ts index 113866017f24b4..f3d59c9607a386 100644 --- a/x-pack/plugins/fleet/server/services/epm/kibana/assets/tag_assets.test.ts +++ b/x-pack/plugins/fleet/server/services/epm/kibana/assets/tag_assets.test.ts @@ -688,55 +688,108 @@ describe('tagKibanaAssets', () => { ); }); - it('should respect SecuritySolution tags', async () => { - savedObjectTagClient.get.mockRejectedValue(new Error('not found')); - savedObjectTagClient.create.mockImplementation(({ name }: { name: string }) => - Promise.resolve({ id: name.toLowerCase(), name }) - ); - const kibanaAssets = { - dashboard: [ - { id: 'dashboard1', type: 'dashboard' }, - { id: 'dashboard2', type: 'dashboard' }, - { id: 'search_id1', type: 'search' }, - { id: 'search_id2', type: 'search' }, - ], - } as any; - const assetTags = [ - { - text: 'Security Solution', - asset_types: ['dashboard'], - }, - ]; - await tagKibanaAssets({ - savedObjectTagAssignmentService, - savedObjectTagClient, - kibanaAssets, - pkgTitle: 'TestPackage', - pkgName: 'test-pkg', - spaceId: 'default', - importedAssets: [], - assetTags, + describe('Security Solution tag', () => { + it('creates tag in default space', async () => { + savedObjectTagClient.get.mockRejectedValue(new Error('not found')); + savedObjectTagClient.create.mockImplementation(({ name }: { name: string }) => + Promise.resolve({ id: name.toLowerCase(), name }) + ); + const kibanaAssets = { + dashboard: [ + { id: 'dashboard1', type: 'dashboard' }, + { id: 'dashboard2', type: 'dashboard' }, + { id: 'search_id1', type: 'search' }, + { id: 'search_id2', type: 'search' }, + ], + } as any; + const assetTags = [ + { + text: 'Security Solution', + asset_types: ['dashboard'], + }, + ]; + await tagKibanaAssets({ + savedObjectTagAssignmentService, + savedObjectTagClient, + kibanaAssets, + pkgTitle: 'TestPackage', + pkgName: 'test-pkg', + spaceId: 'default', + importedAssets: [], + assetTags, + }); + expect(savedObjectTagClient.create).toHaveBeenCalledWith( + managedTagPayloadArg1, + managedTagPayloadArg2 + ); + expect(savedObjectTagClient.create).toHaveBeenCalledWith( + { + color: '#4DD2CA', + description: '', + name: 'TestPackage', + }, + { id: 'fleet-pkg-test-pkg-default', overwrite: true, refresh: false } + ); + expect(savedObjectTagClient.create).toHaveBeenCalledWith( + { + color: expect.any(String), + description: 'Tag defined in package-spec', + name: 'Security Solution', + }, + { id: 'security-solution-default', overwrite: true, refresh: false } + ); + }); + + it('creates tag in non-default space', async () => { + savedObjectTagClient.get.mockRejectedValue(new Error('not found')); + savedObjectTagClient.create.mockImplementation(({ name }: { name: string }) => + Promise.resolve({ id: name.toLowerCase(), name }) + ); + const kibanaAssets = { + dashboard: [ + { id: 'dashboard1', type: 'dashboard' }, + { id: 'dashboard2', type: 'dashboard' }, + { id: 'search_id1', type: 'search' }, + { id: 'search_id2', type: 'search' }, + ], + } as any; + const assetTags = [ + { + text: 'Security Solution', + asset_types: ['dashboard'], + }, + ]; + await tagKibanaAssets({ + savedObjectTagAssignmentService, + savedObjectTagClient, + kibanaAssets, + pkgTitle: 'TestPackage', + pkgName: 'test-pkg', + spaceId: 'my-secondary-space', + importedAssets: [], + assetTags, + }); + expect(savedObjectTagClient.create).toHaveBeenCalledWith(managedTagPayloadArg1, { + ...managedTagPayloadArg2, + id: 'fleet-managed-my-secondary-space', + }); + expect(savedObjectTagClient.create).toHaveBeenCalledWith( + { + color: expect.any(String), + description: '', + name: 'TestPackage', + }, + { id: 'fleet-pkg-test-pkg-my-secondary-space', overwrite: true, refresh: false } + ); + expect(savedObjectTagClient.create).toHaveBeenCalledWith( + { + color: expect.anything(), + description: 'Tag defined in package-spec', + name: 'Security Solution', + }, + { id: 'security-solution-my-secondary-space', overwrite: true, refresh: false } + ); }); - expect(savedObjectTagClient.create).toHaveBeenCalledWith( - managedTagPayloadArg1, - managedTagPayloadArg2 - ); - expect(savedObjectTagClient.create).toHaveBeenCalledWith( - { - color: '#4DD2CA', - description: '', - name: 'TestPackage', - }, - { id: 'fleet-pkg-test-pkg-default', overwrite: true, refresh: false } - ); - expect(savedObjectTagClient.create).toHaveBeenCalledWith( - { - color: expect.any(String), - description: 'Tag defined in package-spec', - name: 'Security Solution', - }, - { id: 'security-solution-default', overwrite: true, refresh: false } - ); }); it('should only call savedObjectTagClient.create for basic tags if there are no assetTags to assign', async () => { diff --git a/x-pack/plugins/fleet/server/services/epm/kibana/assets/tag_assets.ts b/x-pack/plugins/fleet/server/services/epm/kibana/assets/tag_assets.ts index 86d39a5582607e..5e7d4477bbbd3c 100644 --- a/x-pack/plugins/fleet/server/services/epm/kibana/assets/tag_assets.ts +++ b/x-pack/plugins/fleet/server/services/epm/kibana/assets/tag_assets.ts @@ -37,7 +37,7 @@ const PACKAGE_TAG_COLOR = '#4DD2CA'; const MANAGED_TAG_NAME = 'Managed'; const LEGACY_MANAGED_TAG_ID = 'managed'; const SECURITY_SOLUTION_TAG_NAME = 'Security Solution'; -const SECURITY_SOLUTION_TAG_ID = 'security-solution-default'; +const SECURITY_SOLUTION_TAG_ID_BASE = 'security-solution'; // the tag service only accepts 6-digits hex colors const TAG_COLORS = [ @@ -65,8 +65,10 @@ const getLegacyPackageTagId = (pkgName: string) => pkgName; In that case return id `security-solution-default` */ export const getPackageSpecTagId = (spaceId: string, pkgName: string, tagName: string) => { - if (tagName.toLowerCase() === SECURITY_SOLUTION_TAG_NAME.toLowerCase()) - return SECURITY_SOLUTION_TAG_ID; + if (tagName.toLowerCase() === SECURITY_SOLUTION_TAG_NAME.toLowerCase()) { + return `${SECURITY_SOLUTION_TAG_ID_BASE}-${spaceId}`; + } + // UUID v5 needs a namespace (uuid.DNS) to generate a predictable uuid const uniqueId = uuidv5(`${tagName.toLowerCase()}`, uuidv5.DNS); return `fleet-shared-tag-${pkgName}-${uniqueId}-${spaceId}`; diff --git a/x-pack/plugins/fleet/server/services/epm/packages/custom_integrations/utils.test.ts b/x-pack/plugins/fleet/server/services/epm/packages/custom_integrations/utils.test.ts new file mode 100644 index 00000000000000..1ba03cbd045a40 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/epm/packages/custom_integrations/utils.test.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { convertStringToTitle } from './utils'; + +describe('convertStringToTitle', () => { + it('works without underscore: test', () => { + expect(convertStringToTitle('test')).toBe('Test'); + }); + + it('works with one underscore test_test', () => { + expect(convertStringToTitle('test_test')).toBe('Test Test'); + }); + + it('works with double underscore: test__test', () => { + expect(convertStringToTitle('test__test')).toBe('Test Test'); + }); +}); diff --git a/x-pack/plugins/fleet/server/services/epm/packages/custom_integrations/utils.ts b/x-pack/plugins/fleet/server/services/epm/packages/custom_integrations/utils.ts index d90c12d231f74b..4c18941f6638df 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/custom_integrations/utils.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/custom_integrations/utils.ts @@ -8,6 +8,7 @@ export const convertStringToTitle = (name: string) => { return name .split('_') + .filter((word) => word.length > 0) .map((word) => { return word[0].toUpperCase() + word.substring(1); }) diff --git a/x-pack/plugins/fleet/server/services/metrics/fleet_metrics_task.ts b/x-pack/plugins/fleet/server/services/metrics/fleet_metrics_task.ts index 03f44bf96ae963..bd72976fa603c6 100644 --- a/x-pack/plugins/fleet/server/services/metrics/fleet_metrics_task.ts +++ b/x-pack/plugins/fleet/server/services/metrics/fleet_metrics_task.ts @@ -72,10 +72,10 @@ export class FleetMetricsTask { return; } if (!this.esClient) { - appContextService.getLogger().info('esClient not set, skipping Fleet metrics task'); + appContextService.getLogger().debug('esClient not set, skipping Fleet metrics task'); return; } - appContextService.getLogger().info('Running Fleet metrics task'); + appContextService.getLogger().debug('Running Fleet metrics task'); try { const agentMetrics = await fetchAgentMetrics(); diff --git a/x-pack/plugins/fleet/server/types/rest_spec/agent.ts b/x-pack/plugins/fleet/server/types/rest_spec/agent.ts index e90fda99cdff90..902380fc28ca1f 100644 --- a/x-pack/plugins/fleet/server/types/rest_spec/agent.ts +++ b/x-pack/plugins/fleet/server/types/rest_spec/agent.ts @@ -95,6 +95,7 @@ export const PostBulkAgentUnenrollRequestSchema = { force: schema.maybe(schema.boolean()), revoke: schema.maybe(schema.boolean()), batchSize: schema.maybe(schema.number()), + includeInactive: schema.maybe(schema.boolean()), }), }; diff --git a/x-pack/plugins/global_search/server/services/context.test.ts b/x-pack/plugins/global_search/server/services/context.test.ts index f90c0bc0f6c84b..eebf583b6fc3af 100644 --- a/x-pack/plugins/global_search/server/services/context.test.ts +++ b/x-pack/plugins/global_search/server/services/context.test.ts @@ -26,7 +26,9 @@ describe('getContextFactory', () => { expect(coreStart.uiSettings.asScopedToClient).toHaveBeenCalledWith(soClient); expect(coreStart.capabilities.resolveCapabilities).toHaveBeenCalledTimes(1); - expect(coreStart.capabilities.resolveCapabilities).toHaveBeenCalledWith(request); + expect(coreStart.capabilities.resolveCapabilities).toHaveBeenCalledWith(request, { + capabilityPath: '*', + }); expect(context).toEqual({ core: { diff --git a/x-pack/plugins/global_search/server/services/context.ts b/x-pack/plugins/global_search/server/services/context.ts index 02d86c909ab72d..06be3f4acd35cb 100644 --- a/x-pack/plugins/global_search/server/services/context.ts +++ b/x-pack/plugins/global_search/server/services/context.ts @@ -27,7 +27,9 @@ export const getContextFactory = uiSettings: { client: coreStart.uiSettings.asScopedToClient(soClient), }, - capabilities: from(coreStart.capabilities.resolveCapabilities(request)), + capabilities: from( + coreStart.capabilities.resolveCapabilities(request, { capabilityPath: '*' }) + ), }, }; }; diff --git a/x-pack/plugins/index_management/README.md b/x-pack/plugins/index_management/README.md index 8673447fc577ca..867ccbd74335c9 100644 --- a/x-pack/plugins/index_management/README.md +++ b/x-pack/plugins/index_management/README.md @@ -34,14 +34,15 @@ interface IndexDetailsTab { An example of adding an ILM tab can be found in [this file](https://github.com/elastic/kibana/blob/main/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/index_lifecycle_summary.tsx#L250). -- `setIndexOverviewContent(content: IndexOverviewContent)`: replaces the default content in the overview tab (code block describing adding documents to the index) with the custom content. The custom content has the following interface: +- `setIndexOverviewContent(content: IndexContent)`: replaces the default content in the overview tab (code block describing adding documents to the index) with the custom content. The custom content has the following interface: ```ts -interface IndexOverviewContent { +interface IndexContent { renderContent: (args: { index: Index; getUrlForApp: ApplicationStart['getUrlForApp']; }) => ReturnType; ``` +- `setIndexMappingsContent(content: IndexContent)`: adds content to the mappings tab of the index details page. The content is displayed in the right bottom corner, below the mappings docs link. ## Indices tab diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.test.tsx index afdfa15c917aa1..24938682ac5199 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.test.tsx +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.test.tsx @@ -501,6 +501,28 @@ describe('', () => { expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests + 1); }); }); + + it('renders the content set via the extensions service', async () => { + const mappingsContent = 'test mappings extension'; + await act(async () => { + testBed = await setup({ + httpSetup, + dependencies: { + services: { + extensionsService: { + _indexMappingsContent: { + renderContent: () => mappingsContent, + }, + }, + }, + }, + }); + }); + testBed.component.update(); + await testBed.actions.clickIndexDetailsTab(IndexDetailsSection.Mappings); + const content = testBed.actions.getActiveTabContent(); + expect(content).toContain(mappingsContent); + }); }); describe('Settings tab', () => { diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_clone.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_clone.test.tsx index b81c4ce59ea3c6..91afa2ff3947af 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_clone.test.tsx +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_clone.test.tsx @@ -12,7 +12,7 @@ import { API_BASE_PATH } from '../../../common/constants'; import { getComposableTemplate } from '../../../test/fixtures'; import { setupEnvironment } from '../helpers'; -import { TEMPLATE_NAME, INDEX_PATTERNS as DEFAULT_INDEX_PATTERNS, MAPPINGS } from './constants'; +import { TEMPLATE_NAME, INDEX_PATTERNS as DEFAULT_INDEX_PATTERNS } from './constants'; import { setup } from './template_clone.helpers'; import { TemplateFormTestBed } from './template_form.helpers'; @@ -37,9 +37,8 @@ jest.mock('@elastic/eui', () => { const templateToClone = getComposableTemplate({ name: TEMPLATE_NAME, indexPatterns: ['indexPattern1'], - template: { - mappings: MAPPINGS, - }, + template: {}, + allowAutoCreate: true, }); describe('', () => { @@ -97,7 +96,7 @@ describe('', () => { actions.clickNextButton(); }); - const { priority, version, _kbnMeta } = templateToClone; + const { template, priority, version, _kbnMeta, allowAutoCreate } = templateToClone; expect(httpSetup.post).toHaveBeenLastCalledWith( `${API_BASE_PATH}/index_templates`, expect.objectContaining({ @@ -106,7 +105,9 @@ describe('', () => { indexPatterns: DEFAULT_INDEX_PATTERNS, priority, version, + allowAutoCreate, _kbnMeta, + template, }), }) ); diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.test.tsx index 8fb60ec708fd0e..fd373efb6d17da 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.test.tsx +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.test.tsx @@ -532,6 +532,13 @@ describe('', () => { await actions.completeStepOne({ name: TEMPLATE_NAME, indexPatterns: DEFAULT_INDEX_PATTERNS, + dataStream: {}, + lifecycle: { + enabled: true, + value: 1, + unit: 'd', + }, + allowAutoCreate: true, }); // Component templates await actions.completeStepTwo('test_component_template_1'); @@ -558,6 +565,8 @@ describe('', () => { body: JSON.stringify({ name: TEMPLATE_NAME, indexPatterns: DEFAULT_INDEX_PATTERNS, + allowAutoCreate: true, + dataStream: {}, _kbnMeta: { type: 'default', hasDatastream: false, @@ -580,6 +589,10 @@ describe('', () => { }, }, aliases: ALIASES, + lifecycle: { + enabled: true, + data_retention: '1d', + }, }, }), }) @@ -619,6 +632,11 @@ describe('', () => { name: TEMPLATE_NAME, indexPatterns: DEFAULT_INDEX_PATTERNS, dataStream: {}, + lifecycle: { + enabled: true, + value: 1, + unit: 'd', + }, }); await act(async () => { @@ -629,8 +647,15 @@ describe('', () => { `${API_BASE_PATH}/index_templates/simulate`, expect.objectContaining({ body: JSON.stringify({ + template: { + lifecycle: { + enabled: true, + data_retention: '1d', + }, + }, index_patterns: DEFAULT_INDEX_PATTERNS, data_stream: {}, + allow_auto_create: false, }), }) ); diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_edit.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_edit.test.tsx index 359f1472aec62b..56cfbf7968ecc2 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_edit.test.tsx +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_edit.test.tsx @@ -119,6 +119,11 @@ describe('', () => { name: 'test', indexPatterns: ['myPattern*'], version: 1, + lifecycle: { + enabled: true, + value: 1, + unit: 'd', + }, }); // Component templates await actions.completeStepTwo(); @@ -140,6 +145,7 @@ describe('', () => { name: 'test', indexPatterns: ['myPattern*'], version: 1, + allowAutoCreate: false, dataStream: { hidden: true, anyUnknownKey: 'should_be_kept', @@ -149,6 +155,12 @@ describe('', () => { hasDatastream: true, isLegacy: false, }, + template: { + lifecycle: { + enabled: true, + data_retention: '1d', + }, + }, }), }) ); @@ -198,6 +210,7 @@ describe('', () => { await actions.completeStepOne({ indexPatterns: UPDATED_INDEX_PATTERN, priority: 3, + allowAutoCreate: true, }); // Component templates await actions.completeStepTwo(); @@ -252,6 +265,7 @@ describe('', () => { indexPatterns: UPDATED_INDEX_PATTERN, priority: 3, version: templateToEdit.version, + allowAutoCreate: true, _kbnMeta: { type: 'default', hasDatastream: false, diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_form.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_form.helpers.ts index bf16e8e5e803d5..8b98fb769959f6 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_form.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_form.helpers.ts @@ -146,6 +146,8 @@ export const formSetup = async (initTestBed: SetupFunc) => { priority, version, dataStream, + lifecycle, + allowAutoCreate, }: Partial = {}) => { const { component, form, find } = testBed; @@ -183,8 +185,27 @@ export const formSetup = async (initTestBed: SetupFunc) => { if (version) { form.setInputValue('versionField.input', JSON.stringify(version)); } + }); + component.update(); + + if (lifecycle && lifecycle.enabled) { + act(() => { + form.toggleEuiSwitch('dataRetentionToggle.input'); + }); + component.update(); + + act(() => { + form.setInputValue('valueDataRetentionField', String(lifecycle.value)); + }); + } + + await act(async () => { + if (allowAutoCreate) { + form.toggleEuiSwitch('allowAutoCreateField.input'); + } clickNextButton(); + jest.advanceTimersByTime(0); }); component.update(); @@ -210,7 +231,6 @@ export const formSetup = async (initTestBed: SetupFunc) => { await act(async () => { clickNextButton(); - jest.advanceTimersByTime(0); }); component.update(); @@ -332,6 +352,8 @@ export type TestSubjects = | 'orderField.input' | 'priorityField.input' | 'dataStreamField.input' + | 'dataRetentionToggle.input' + | 'allowAutoCreateField.input' | 'pageTitle' | 'previewTab' | 'removeFieldButton' @@ -355,6 +377,7 @@ export type TestSubjects = | 'aliasesEditor' | 'settingsEditor' | 'versionField.input' + | 'valueDataRetentionField' | 'mappingsEditor.formTab' | 'mappingsEditor.advancedConfiguration.sizeEnabledToggle' | 'previewIndexTemplate'; diff --git a/x-pack/plugins/index_management/common/lib/data_stream_serialization.ts b/x-pack/plugins/index_management/common/lib/data_stream_serialization.ts index d7bd1d9e92f957..ceedd072139aa7 100644 --- a/x-pack/plugins/index_management/common/lib/data_stream_serialization.ts +++ b/x-pack/plugins/index_management/common/lib/data_stream_serialization.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { DataStream, EnhancedDataStreamFromEs, Health } from '../types'; +import { DataStream, EnhancedDataStreamFromEs, Health, DataRetention } from '../types'; export function deserializeDataStream(dataStreamFromEs: EnhancedDataStreamFromEs): DataStream { const { @@ -83,3 +83,43 @@ export const splitSizeAndUnits = (field: string): { size: string; unit: string } unit, }; }; + +export const serializeAsESLifecycle = (lifecycle?: DataRetention): DataStream['lifecycle'] => { + if (!lifecycle || !lifecycle?.enabled) { + return undefined; + } + + const { infiniteDataRetention, value, unit } = lifecycle; + + if (infiniteDataRetention) { + return { + enabled: true, + }; + } + + return { + enabled: true, + data_retention: `${value}${unit}`, + }; +}; + +export const deserializeESLifecycle = (lifecycle?: DataStream['lifecycle']): DataRetention => { + if (!lifecycle || !lifecycle?.enabled) { + return { enabled: false }; + } + + if (!lifecycle.data_retention) { + return { + enabled: true, + infiniteDataRetention: true, + }; + } + + const { size, unit } = splitSizeAndUnits(lifecycle.data_retention as string); + + return { + enabled: true, + value: Number(size), + unit, + }; +}; 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 ce49c32cd82715..bb871b1556d300 100644 --- a/x-pack/plugins/index_management/common/lib/template_serialization.ts +++ b/x-pack/plugins/index_management/common/lib/template_serialization.ts @@ -12,12 +12,21 @@ import { TemplateListItem, TemplateType, } from '../types'; +import { deserializeESLifecycle } from './data_stream_serialization'; const hasEntries = (data: object = {}) => Object.entries(data).length > 0; export function serializeTemplate(templateDeserialized: TemplateDeserialized): TemplateSerialized { - const { version, priority, indexPatterns, template, composedOf, dataStream, _meta } = - templateDeserialized; + const { + version, + priority, + indexPatterns, + template, + composedOf, + dataStream, + _meta, + allowAutoCreate, + } = templateDeserialized; return { version, @@ -26,6 +35,7 @@ export function serializeTemplate(templateDeserialized: TemplateDeserialized): T index_patterns: indexPatterns, data_stream: dataStream, composed_of: composedOf, + allow_auto_create: allowAutoCreate, _meta, }; } @@ -43,6 +53,7 @@ export function deserializeTemplate( _meta, composed_of: composedOf, data_stream: dataStream, + allow_auto_create: allowAutoCreate, } = templateEs; const { settings } = template; @@ -59,11 +70,13 @@ export function deserializeTemplate( name, version, priority, + ...(template.lifecycle ? { lifecycle: deserializeESLifecycle(template.lifecycle) } : {}), indexPatterns: indexPatterns.sort(), template, ilmPolicy: settings?.index?.lifecycle, composedOf, dataStream, + allowAutoCreate, _meta, _kbnMeta: { type, 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 d2842b04863039..68c58aefc9d069 100644 --- a/x-pack/plugins/index_management/common/types/component_templates.ts +++ b/x-pack/plugins/index_management/common/types/component_templates.ts @@ -8,15 +8,18 @@ import { IndexSettings } from './indices'; import { Aliases } from './aliases'; import { Mappings } from './mappings'; +import { DataStream, DataRetention } from '.'; export interface ComponentTemplateSerialized { template: { settings?: IndexSettings; aliases?: Aliases; mappings?: Mappings; + lifecycle?: DataStream['lifecycle']; }; version?: number; _meta?: { [key: string]: any }; + lifecycle?: DataRetention; } export interface ComponentTemplateDeserialized extends ComponentTemplateSerialized { diff --git a/x-pack/plugins/index_management/common/types/data_streams.ts b/x-pack/plugins/index_management/common/types/data_streams.ts index 80a4be29ee9240..f3890dd032a5ae 100644 --- a/x-pack/plugins/index_management/common/types/data_streams.ts +++ b/x-pack/plugins/index_management/common/types/data_streams.ts @@ -75,3 +75,10 @@ export interface DataStreamIndex { preferILM: boolean; managedBy: string; } + +export interface DataRetention { + enabled: boolean; + infiniteDataRetention?: boolean; + value?: number; + unit?: string; +} diff --git a/x-pack/plugins/index_management/common/types/index.ts b/x-pack/plugins/index_management/common/types/index.ts index f3f2315cdd15be..ef2e8a389c079f 100644 --- a/x-pack/plugins/index_management/common/types/index.ts +++ b/x-pack/plugins/index_management/common/types/index.ts @@ -13,7 +13,13 @@ export * from './mappings'; export * from './templates'; -export type { EnhancedDataStreamFromEs, Health, DataStream, DataStreamIndex } from './data_streams'; +export type { + EnhancedDataStreamFromEs, + Health, + DataStream, + DataStreamIndex, + DataRetention, +} from './data_streams'; export * from './component_templates'; diff --git a/x-pack/plugins/index_management/common/types/templates.ts b/x-pack/plugins/index_management/common/types/templates.ts index ca1433053cb9d7..7d85bb4d20ae07 100644 --- a/x-pack/plugins/index_management/common/types/templates.ts +++ b/x-pack/plugins/index_management/common/types/templates.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { DataRetention, DataStream } from './data_streams'; import { IndexSettings } from './indices'; import { Aliases } from './aliases'; import { Mappings } from './mappings'; @@ -18,12 +19,14 @@ export interface TemplateSerialized { settings?: IndexSettings; aliases?: Aliases; mappings?: Mappings; + lifecycle?: DataStream['lifecycle']; }; composed_of?: string[]; version?: number; priority?: number; _meta?: { [key: string]: any }; data_stream?: {}; + allow_auto_create?: boolean; } /** @@ -39,9 +42,11 @@ export interface TemplateDeserialized { aliases?: Aliases; mappings?: Mappings; }; + lifecycle?: DataRetention; composedOf?: string[]; // Composable template only version?: number; priority?: number; // Composable template only + allowAutoCreate?: boolean; order?: number; // Legacy template only ilmPolicy?: { name: string; diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_create.test.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_create.test.tsx index a8ef1278f4d730..eaf9c8389ae958 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_create.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_create.test.tsx @@ -12,6 +12,7 @@ import { breadcrumbService, IndexManagementBreadcrumb } from '../../../../servic import { setupEnvironment } from './helpers'; import { API_BASE_PATH } from './helpers/constants'; import { setup, ComponentTemplateCreateTestBed } from './helpers/component_template_create.helpers'; +import { serializeAsESLifecycle } from '../../../../../../common/lib/data_stream_serialization'; jest.mock('@kbn/kibana-react-plugin/public', () => { const original = jest.requireActual('@kbn/kibana-react-plugin/public'); @@ -98,6 +99,19 @@ describe('', () => { expect(exists('metaEditor')).toBe(true); }); + test('should toggle the data retention field', async () => { + const { exists, component, form } = testBed; + + expect(exists('valueDataRetentionField')).toBe(false); + + await act(async () => { + form.toggleEuiSwitch('dataRetentionToggle.input'); + }); + component.update(); + + expect(exists('valueDataRetentionField')).toBe(true); + }); + describe('Validation', () => { test('should require a name', async () => { const { form, actions, component, find } = testBed; @@ -120,6 +134,11 @@ describe('', () => { const COMPONENT_TEMPLATE_NAME = 'comp-1'; const SETTINGS = { number_of_shards: 1 }; const ALIASES = { my_alias: {} }; + const LIFECYCLE = { + enabled: true, + value: 2, + unit: 'd', + }; const BOOLEAN_MAPPING_FIELD = { name: 'boolean_datatype', @@ -136,7 +155,10 @@ describe('', () => { component.update(); // Complete step 1 (logistics) - await actions.completeStepLogistics({ name: COMPONENT_TEMPLATE_NAME }); + await actions.completeStepLogistics({ + name: COMPONENT_TEMPLATE_NAME, + lifecycle: LIFECYCLE, + }); // Complete step 2 (index settings) await actions.completeStepSettings(SETTINGS); @@ -199,6 +221,7 @@ describe('', () => { }, }, aliases: ALIASES, + lifecycle: serializeAsESLifecycle(LIFECYCLE), }, _kbnMeta: { usedBy: [], isManaged: false }, }), 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 95495af1272c33..501d14042def37 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 @@ -19,6 +19,7 @@ const COMPONENT_TEMPLATE: ComponentTemplateDeserialized = { mappings: { properties: { ip_address: { type: 'ip' } } }, aliases: { mydata: {} }, settings: { number_of_shards: 1 }, + lifecycle: { enabled: true, data_retention: '4d' }, }, version: 1, _meta: { description: 'component template test' }, @@ -72,6 +73,7 @@ describe('', () => { expect(exists('summaryTabContent.usedByTitle')).toBe(true); expect(exists('summaryTabContent.versionTitle')).toBe(true); expect(exists('summaryTabContent.metaTitle')).toBe(true); + expect(exists('summaryTabContent.dataRetentionTitle')).toBe(true); // [Settings tab] Navigate to tab and verify content act(() => { 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 e2f41bbe246f2f..44a78e0d0666f6 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 @@ -78,6 +78,7 @@ export type ComponentTemplateDetailsTestSubjects = | 'summaryTabContent.usedByTitle' | 'summaryTabContent.versionTitle' | 'summaryTabContent.metaTitle' + | 'summaryTabContent.dataRetentionTitle' | 'notInUseCallout' | 'aliasesTabContent' | 'noAliasesCallout' 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 0db29dffff5107..d809bb230ffaac 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 @@ -8,6 +8,7 @@ import { act } from 'react-dom/test-utils'; import { TestBed } from '@kbn/test-jest-helpers'; +import { DataRetention } from '../../../../../../../common'; interface MappingField { name: string; @@ -52,11 +53,28 @@ export const getFormActions = (testBed: TestBed) => { .simulate('click'); }; - const completeStepLogistics = async ({ name }: { name: string }) => { + const completeStepLogistics = async ({ + name, + lifecycle, + }: { + name: string; + lifecycle: DataRetention; + }) => { const { form, component } = testBed; // Add name field form.setInputValue('nameField.input', name); + if (lifecycle && lifecycle.enabled) { + act(() => { + form.toggleEuiSwitch('dataRetentionToggle.input'); + }); + component.update(); + + act(() => { + form.setInputValue('valueDataRetentionField', String(lifecycle.value)); + }); + } + await act(async () => { clickNextButton(); }); @@ -164,6 +182,8 @@ export type ComponentTemplateFormTestSubjects = | 'stepReview.content' | 'stepReview.summaryTab' | 'stepReview.requestTab' + | 'valueDataRetentionField' + | 'dataRetentionToggle.input' | 'versionField' | 'aliasesEditor' | 'mappingsEditor' diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_details/tab_summary.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_details/tab_summary.tsx index 3e9f0c38f2a523..cebd88cf759652 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_details/tab_summary.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_details/tab_summary.tsx @@ -19,6 +19,7 @@ import { EuiLink, } from '@elastic/eui'; +import { getLifecycleValue } from '../../../lib/data_streams'; import { ComponentTemplateDeserialized } from '../shared_imports'; import { useComponentTemplatesContext } from '../component_templates_context'; @@ -27,13 +28,15 @@ interface Props { showCallToAction?: boolean; } +const INFINITE_AS_ICON = true; + export const TabSummary: React.FunctionComponent = ({ componentTemplateDetails, showCallToAction, }) => { const { getUrlForApp } = useComponentTemplatesContext(); - const { version, _meta, _kbnMeta } = componentTemplateDetails; + const { version, _meta, _kbnMeta, template } = componentTemplateDetails; const { usedBy } = _kbnMeta; const templateIsInUse = usedBy.length > 0; @@ -118,6 +121,20 @@ export const TabSummary: React.FunctionComponent = ({ )} + {template.lifecycle && ( + <> + + + + + {getLifecycleValue(template.lifecycle, INFINITE_AS_ICON)} + + + )} + {/* Version (optional) */} {typeof version !== 'undefined' && ( <> diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_form/component_template_form.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_form/component_template_form.tsx index 6ec857e9c2c30e..38814ece0c17cf 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_form/component_template_form.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_form/component_template_form.tsx @@ -19,6 +19,10 @@ import { StepMappingsContainer, StepAliasesContainer, } from '../../shared_imports'; +import { + serializeAsESLifecycle, + deserializeESLifecycle, +} from '../../../../../../common/lib/data_stream_serialization'; import { useComponentTemplatesContext } from '../../component_templates_context'; import { StepLogisticsContainer, StepReviewContainer } from './steps'; @@ -96,14 +100,17 @@ export const ComponentTemplateForm = ({ onStepChange, }: Props) => { const { - template: { settings, mappings, aliases }, + template: { settings, mappings, aliases, lifecycle }, ...logistics } = defaultValue; const { documentation } = useComponentTemplatesContext(); const wizardDefaultValue: WizardContent = { - logistics, + logistics: { + ...logistics, + ...(lifecycle ? { lifecycle: deserializeESLifecycle(lifecycle) } : {}), + }, settings, mappings, aliases, @@ -162,6 +169,10 @@ export const ComponentTemplateForm = ({ delete outputTemplate.template.aliases; } + if (outputTemplate.lifecycle) { + delete outputTemplate.lifecycle; + } + return outputTemplate; }; @@ -177,9 +188,14 @@ export const ComponentTemplateForm = ({ settings: wizardData.settings, mappings: wizardData.mappings, aliases: wizardData.aliases, + lifecycle: wizardData.logistics.lifecycle + ? serializeAsESLifecycle(wizardData.logistics.lifecycle) + : undefined, }, }; - return cleanupComponentTemplateObject(outputComponentTemplate); + return cleanupComponentTemplateObject( + outputComponentTemplate as ComponentTemplateDeserialized + ); }, [] ); diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_form/steps/step_logistics.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_form/steps/step_logistics.tsx index 745f2839c5f690..f892e650c1e1bb 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_form/steps/step_logistics.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_form/steps/step_logistics.tsx @@ -24,8 +24,12 @@ import { getFormRow, Field, Forms, + NumericField, JsonEditorField, + useFormData, } from '../../../shared_imports'; +import { DataRetention } from '../../../../../../../common'; +import { UnitField, timeUnits } from '../../../../shared'; import { useComponentTemplatesContext } from '../../../component_templates_context'; import { logisticsFormSchema } from './step_logistics_schema'; @@ -48,6 +52,13 @@ export const StepLogistics: React.FunctionComponent = React.memo( const { isValid: isFormValid, submit, getFormData, subscribe } = form; + const [{ lifecycle }] = useFormData<{ + lifecycle: DataRetention; + }>({ + form, + watch: ['lifecycle.enabled', 'lifecycle.infiniteDataRetention'], + }); + const { documentation } = useComponentTemplatesContext(); const [isMetaVisible, setIsMetaVisible] = useState( @@ -134,6 +145,64 @@ export const StepLogistics: React.FunctionComponent = React.memo( /> + {/* Data retention field */} + + } + description={ + <> + + + + + } + > + {lifecycle?.enabled && ( + + } + componentProps={{ + euiFieldProps: { + disabled: lifecycle?.infiniteDataRetention, + 'data-test-subj': 'valueDataRetentionField', + min: 1, + append: ( + + ), + }, + }} + /> + )} + + {/* version field */} { + // If infiniteRetentionPeriod is set, we dont need to validate the data retention field + if (formData['lifecycle.infiniteDataRetention']) { + return undefined; + } + + if (!value) { + return { + message: i18n.translate( + 'xpack.idxMgmt.dataStreamsDetailsPanel.stepLogistics.dataRetentionFieldRequiredError', + { + defaultMessage: 'A data retention value is required.', + } + ), + }; + } + + if (value <= 0) { + return { + message: i18n.translate( + 'xpack.idxMgmt.dataStreamsDetailsPanel.stepLogistics.dataRetentionFieldNonNegativeError', + { + defaultMessage: `A positive value is required.`, + } + ), + }; + } + + if (value % 1 !== 0) { + return { + message: i18n.translate( + 'xpack.idxMgmt.dataStreamsDetailsPanel.stepLogistics.dataRetentionFieldDecimalError', + { + defaultMessage: `The value should be an integer number.`, + } + ), + }; + } + }, + }, + ], + }, + 'lifecycle.unit': { + type: FIELD_TYPES.TEXT, + label: i18n.translate( + 'xpack.idxMgmt.componentTemplateForm.stepLogistics.fieldDataRetentionUnitLabel', + { + defaultMessage: 'Time unit', + } + ), + defaultValue: 'd', + }, version: { type: FIELD_TYPES.NUMBER, label: i18n.translate('xpack.idxMgmt.componentTemplateForm.stepLogistics.versionFieldLabel', { diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_form/steps/step_review.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_form/steps/step_review.tsx index 159f25ab010b7d..72746d426d2e10 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_form/steps/step_review.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_form/steps/step_review.tsx @@ -28,7 +28,9 @@ import { serializeComponentTemplate, } from '../../../shared_imports'; import { MANAGED_BY_FLEET } from '../../../constants'; +import { getLifecycleValue } from '../../../../../lib/data_streams'; +const INFINITE_AS_ICON = true; const { stripEmptyFields } = serializers; const getDescriptionText = (data: any) => { @@ -123,6 +125,17 @@ export const StepReview: React.FunctionComponent = React.memo( {getDescriptionText(serializedTemplate?.aliases)} + + {/* Data retention */} + + + + + {getLifecycleValue(serializedTemplate?.lifecycle, INFINITE_AS_ICON)} +
    {isFleetDatastreamsVisible && dataStreams && ( diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/shared_imports.ts b/x-pack/plugins/index_management/public/application/components/component_templates/shared_imports.ts index 78dd0395b6e341..d70ed32e2cdf82 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/shared_imports.ts +++ b/x-pack/plugins/index_management/public/application/components/component_templates/shared_imports.ts @@ -41,12 +41,14 @@ export { useForm, Form, getUseField, + useFormData, } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; export { getFormRow, Field, JsonEditorField, + NumericField, } from '@kbn/es-ui-shared-plugin/static/forms/components'; export { isJSON } from '@kbn/es-ui-shared-plugin/static/validators/string'; diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/edit_data_retention_modal/unit_field.tsx b/x-pack/plugins/index_management/public/application/components/shared/fields/unit_field.tsx similarity index 97% rename from x-pack/plugins/index_management/public/application/sections/home/data_stream_list/edit_data_retention_modal/unit_field.tsx rename to x-pack/plugins/index_management/public/application/components/shared/fields/unit_field.tsx index fc12b2ce9edafc..8a01118a5f0957 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/edit_data_retention_modal/unit_field.tsx +++ b/x-pack/plugins/index_management/public/application/components/shared/fields/unit_field.tsx @@ -7,7 +7,7 @@ import React, { FunctionComponent, useState } from 'react'; import { EuiFilterSelectItem, EuiPopover, EuiButtonEmpty } from '@elastic/eui'; -import { UseField } from '../../../../../shared_imports'; +import { UseField } from '../../../../shared_imports'; interface Props { path: string; diff --git a/x-pack/plugins/index_management/public/application/components/shared/index.ts b/x-pack/plugins/index_management/public/application/components/shared/index.ts index 06899e202ef82a..66681ab20c1778 100644 --- a/x-pack/plugins/index_management/public/application/components/shared/index.ts +++ b/x-pack/plugins/index_management/public/application/components/shared/index.ts @@ -6,6 +6,7 @@ */ export type { CommonWizardSteps } from './components'; + export { TabAliases, TabMappings, @@ -15,3 +16,6 @@ export { StepSettingsContainer, TemplateContentIndicator, } from './components'; + +export { UnitField } from './fields/unit_field'; +export { timeUnits } from '../../constants/time_units'; diff --git a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_logistics.tsx b/x-pack/plugins/index_management/public/application/components/template_form/steps/step_logistics.tsx index f818446b32ebe3..5307852d3c9e32 100644 --- a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_logistics.tsx +++ b/x-pack/plugins/index_management/public/application/components/template_form/steps/step_logistics.tsx @@ -26,7 +26,10 @@ import { Field, Forms, JsonEditorField, + NumericField, } from '../../../../shared_imports'; +import { UnitField, timeUnits } from '../../shared'; +import { DataRetention } from '../../../../../common'; import { documentationService } from '../../../services/documentation'; import { schemas, nameConfig, nameConfigWithoutValidations } from '../template_form_schemas'; @@ -112,6 +115,32 @@ function getFieldsMeta(esDocsBase: string) { }), testSubject: 'versionField', }, + dataRetention: { + title: i18n.translate('xpack.idxMgmt.templateForm.stepLogistics.dataRetentionTitle', { + defaultMessage: 'Data retention', + }), + description: i18n.translate( + 'xpack.idxMgmt.templateForm.stepLogistics.dataRetentionDescription', + { + defaultMessage: + 'Data will be kept at least this long before being automatically deleted.', + } + ), + unitTestSubject: 'unitDataRetentionField', + }, + allowAutoCreate: { + title: i18n.translate('xpack.idxMgmt.templateForm.stepLogistics.allowAutoCreateTitle', { + defaultMessage: 'Allow auto create', + }), + description: i18n.translate( + 'xpack.idxMgmt.templateForm.stepLogistics.allowAutoCreateDescription', + { + defaultMessage: + 'Indices can be automatically created even if auto-creation of indices is disabled via actions.auto_create_index.', + } + ), + testSubject: 'allowAutoCreateField', + }, }; } @@ -164,9 +193,18 @@ export const StepLogistics: React.FunctionComponent = React.memo( getFormData, } = form; - const [{ addMeta }] = useFormData<{ addMeta: boolean }>({ + const [{ addMeta, doCreateDataStream, lifecycle }] = useFormData<{ + addMeta: boolean; + lifecycle: DataRetention; + doCreateDataStream: boolean; + }>({ form, - watch: 'addMeta', + watch: [ + 'addMeta', + 'lifecycle.enabled', + 'lifecycle.infiniteDataRetention', + 'doCreateDataStream', + ], }); /** @@ -185,9 +223,16 @@ export const StepLogistics: React.FunctionComponent = React.memo( }); }, [onChange, isFormValid, validate, getFormData]); - const { name, indexPatterns, createDataStream, order, priority, version } = getFieldsMeta( - documentationService.getEsDocsBase() - ); + const { + name, + indexPatterns, + createDataStream, + order, + priority, + version, + dataRetention, + allowAutoCreate, + } = getFieldsMeta(documentationService.getEsDocsBase()); return ( <> @@ -260,6 +305,61 @@ export const StepLogistics: React.FunctionComponent = React.memo( )} + {/* + Since data stream and data retention are settings that are only allowed for non legacy, + we only need to check if data stream is set to true to show the data retention. + */} + {doCreateDataStream && ( + + {dataRetention.description} + + + + } + > + {lifecycle?.enabled && ( + + } + componentProps={{ + euiFieldProps: { + disabled: lifecycle?.infiniteDataRetention, + 'data-test-subj': 'valueDataRetentionField', + min: 1, + append: ( + + ), + }, + }} + /> + )} + + )} + {/* Order */} {isLegacy && ( @@ -294,6 +394,16 @@ export const StepLogistics: React.FunctionComponent = React.memo( /> + {/* Allow auto create */} + {isLegacy === false && ( + + + + )} + {/* _meta */} {isLegacy === false && ( ( ( /> ); -const getDescriptionText = (data: any) => { - const hasEntries = data && Object.entries(data).length > 0; +const getDescriptionText = (data: Aliases | boolean | undefined) => { + const hasEntries = typeof data === 'boolean' ? data : data && Object.entries(data).length > 0; return hasEntries ? ( = React.memo( indexPatterns, version, order, + template: indexTemplate, priority, + allowAutoCreate, composedOf, _meta, _kbnMeta: { isLegacy }, @@ -108,6 +112,7 @@ export const StepReview: React.FunctionComponent = React.memo( const serializedMappings = getTemplateParameter(serializedTemplate, 'mappings'); const serializedSettings = getTemplateParameter(serializedTemplate, 'settings'); const serializedAliases = getTemplateParameter(serializedTemplate, 'aliases'); + const serializedLifecycle = (indexTemplate as TemplateDeserialized)?.lifecycle; const numIndexPatterns = indexPatterns!.length; @@ -186,6 +191,21 @@ export const StepReview: React.FunctionComponent = React.memo( {version ? version : } + {/* Allow auto create */} + {isLegacy !== true && ( + <> + + + + + {getDescriptionText(allowAutoCreate)} + + + )} + {/* components */} {isLegacy !== true && ( <> @@ -258,6 +278,20 @@ export const StepReview: React.FunctionComponent = React.memo( {getDescriptionText(serializedAliases)} + {isLegacy !== true && serializedLifecycle?.enabled && ( + <> + + + + + {getLifecycleValue(serializedLifecycle, INFINITE_AS_ICON)} + + + )} + {/* Metadata (optional) */} {isLegacy !== true && _meta && ( <> diff --git a/x-pack/plugins/index_management/public/application/components/template_form/template_form.tsx b/x-pack/plugins/index_management/public/application/components/template_form/template_form.tsx index 13797f1dfc05bb..9d494e0a558d92 100644 --- a/x-pack/plugins/index_management/public/application/components/template_form/template_form.tsx +++ b/x-pack/plugins/index_management/public/application/components/template_form/template_form.tsx @@ -21,6 +21,7 @@ import { } from '../shared'; import { documentationService } from '../../services/documentation'; import { SectionError } from '../section_error'; +import { serializeAsESLifecycle } from '../../../../common/lib/data_stream_serialization'; import { SimulateTemplateFlyoutContent, SimulateTemplateProps, @@ -190,6 +191,9 @@ export const TemplateForm = ({ if (Object.keys(outputTemplate.template).length === 0) { delete outputTemplate.template; } + if (outputTemplate.lifecycle) { + delete outputTemplate.lifecycle; + } } return outputTemplate; @@ -206,10 +210,13 @@ export const TemplateForm = ({ settings: wizardData.settings, mappings: wizardData.mappings, aliases: wizardData.aliases, + lifecycle: wizardData.logistics.lifecycle + ? serializeAsESLifecycle(wizardData.logistics.lifecycle) + : undefined, }, }; - return cleanupTemplateObject(outputTemplate); + return cleanupTemplateObject(outputTemplate as TemplateDeserialized); }, [] ); diff --git a/x-pack/plugins/index_management/public/application/components/template_form/template_form_schemas.tsx b/x-pack/plugins/index_management/public/application/components/template_form/template_form_schemas.tsx index 77cbca87c7655b..383b60c3650866 100644 --- a/x-pack/plugins/index_management/public/application/components/template_form/template_form_schemas.tsx +++ b/x-pack/plugins/index_management/public/application/components/template_form/template_form_schemas.tsx @@ -158,6 +158,92 @@ export const schemas: Record = { }), formatters: [toInt], }, + + 'lifecycle.enabled': { + type: FIELD_TYPES.TOGGLE, + label: i18n.translate('xpack.idxMgmt.templateForm.stepLogistics.enableDataRetentionLabel', { + defaultMessage: 'Enable data retention', + }), + defaultValue: false, + }, + 'lifecycle.infiniteDataRetention': { + type: FIELD_TYPES.TOGGLE, + label: i18n.translate('xpack.idxMgmt.templateForm.stepLogistics.infiniteDataRetentionLabel', { + defaultMessage: 'Keep data indefinitely', + }), + defaultValue: false, + }, + 'lifecycle.value': { + type: FIELD_TYPES.TEXT, + label: i18n.translate( + 'xpack.idxMgmt.templateForm.stepLogistics.fieldDataRetentionValueLabel', + { + defaultMessage: 'Data Retention', + } + ), + formatters: [toInt], + validations: [ + { + validator: ({ value, formData }) => { + // If infiniteRetentionPeriod is set, we dont need to validate the data retention field + if (formData['lifecycle.infiniteDataRetention']) { + return undefined; + } + + if (!value) { + return { + message: i18n.translate( + 'xpack.idxMgmt.templateForm.stepLogistics.dataRetentionFieldRequiredError', + { + defaultMessage: 'A data retention value is required.', + } + ), + }; + } + + if (value <= 0) { + return { + message: i18n.translate( + 'xpack.idxMgmt.templateForm.stepLogistics.dataRetentionFieldNonNegativeError', + { + defaultMessage: `A positive value is required.`, + } + ), + }; + } + + if (value % 1 !== 0) { + return { + message: i18n.translate( + 'xpack.idxMgmt.templateForm.stepLogistics.dataRetentionFieldDecimalError', + { + defaultMessage: `The value should be an integer number.`, + } + ), + }; + } + }, + }, + ], + }, + 'lifecycle.unit': { + type: FIELD_TYPES.TEXT, + label: i18n.translate( + 'xpack.idxMgmt.templateForm.stepLogistics.fieldDataRetentionUnitLabel', + { + defaultMessage: 'Time unit', + } + ), + defaultValue: 'd', + }, + + allowAutoCreate: { + type: FIELD_TYPES.TOGGLE, + label: i18n.translate('xpack.idxMgmt.templateForm.stepLogistics.fieldAllowAutoCreateLabel', { + defaultMessage: 'Allow auto create (optional)', + }), + defaultValue: false, + }, _meta: { label: i18n.translate('xpack.idxMgmt.templateForm.stepLogistics.metaFieldEditorLabel', { defaultMessage: '_meta field data (optional)', diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/edit_data_retention_modal/edit_data_retention_modal.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/edit_data_retention_modal/edit_data_retention_modal.tsx index a1ac650ba0cb7f..713fdc8a709d53 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/edit_data_retention_modal/edit_data_retention_modal.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/edit_data_retention_modal/edit_data_retention_modal.tsx @@ -42,7 +42,7 @@ import { splitSizeAndUnits, DataStream } from '../../../../../../common'; import { timeUnits } from '../../../../constants/time_units'; import { isDSLWithILMIndices } from '../../../../lib/data_streams'; import { useAppContext } from '../../../../app_context'; -import { UnitField } from './unit_field'; +import { UnitField } from '../../../../components/shared'; import { updateDataRetention } from '../../../../services/api'; interface Props { diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_content.tsx b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_content.tsx index 0360df733c945a..adeadee6132c03 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_content.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_content.tsx @@ -50,7 +50,7 @@ const defaultTabs: IndexDetailsTab[] = [ name: ( ), - renderTabContent: ({ index }) => , + renderTabContent: ({ index }) => , order: 20, }, { diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_mappings.tsx b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_mappings.tsx index a83eebd395b1e3..c6f74207c92d72 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_mappings.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_mappings.tsx @@ -6,27 +6,18 @@ */ import React, { FunctionComponent, useEffect } from 'react'; -import { - EuiButton, - EuiCodeBlock, - EuiFlexGroup, - EuiFlexItem, - EuiIcon, - EuiLink, - EuiPageTemplate, - EuiPanel, - EuiSpacer, - EuiText, - EuiTitle, -} from '@elastic/eui'; -import { css } from '@emotion/react'; +import { EuiButton, EuiPageTemplate, EuiSpacer, EuiText } from '@elastic/eui'; + import { FormattedMessage } from '@kbn/i18n-react'; import { SectionLoading } from '@kbn/es-ui-shared-plugin/public'; -import { useLoadIndexMappings, documentationService } from '../../../../services'; + +import { DetailsPageMappingsContent } from './details_page_mappings_content'; +import { Index } from '../../../../../../common'; +import { useLoadIndexMappings } from '../../../../services'; import { breadcrumbService, IndexManagementBreadcrumb } from '../../../../services/breadcrumbs'; -export const DetailsPageMappings: FunctionComponent<{ indexName: string }> = ({ indexName }) => { - const { isLoading, data, error, resendRequest } = useLoadIndexMappings(indexName); +export const DetailsPageMappings: FunctionComponent<{ index: Index }> = ({ index }) => { + const { isLoading, data, error, resendRequest } = useLoadIndexMappings(index.name); useEffect(() => { breadcrumbService.setBreadcrumbs(IndexManagementBreadcrumb.indexDetailsMappings); @@ -63,7 +54,7 @@ export const DetailsPageMappings: FunctionComponent<{ indexName: string }> = ({ id="xpack.idxMgmt.indexDetails.mappings.errorDescription" defaultMessage="We encountered an error loading mappings for index {indexName}. Make sure that the index name in the URL is correct and try again." values={{ - indexName, + indexName: index.name, }} />
    @@ -86,82 +77,5 @@ export const DetailsPageMappings: FunctionComponent<{ indexName: string }> = ({ ); } - return ( - // using "rowReverse" to keep docs links on the top of the mappings code block on smaller screen - - - - - - - - - -

    - -

    -
    -
    -
    - - -

    - -

    -
    - - - - -
    -
    - - - - - {JSON.stringify(data, null, 2)} - - - -
    - ); + return ; }; diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_mappings_content.tsx b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_mappings_content.tsx new file mode 100644 index 00000000000000..40544bb18e1ff7 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_mappings_content.tsx @@ -0,0 +1,119 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FunctionComponent } from 'react'; +import { + EuiCodeBlock, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiLink, + EuiPanel, + EuiSpacer, + EuiText, + EuiTitle, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { css } from '@emotion/react'; + +import { Index } from '../../../../../../common'; +import { documentationService } from '../../../../services'; +import { useAppContext } from '../../../../app_context'; + +export const DetailsPageMappingsContent: FunctionComponent<{ index: Index; data: any }> = ({ + index, + data, +}) => { + const { + services: { extensionsService }, + core: { getUrlForApp }, + } = useAppContext(); + return ( + // using "rowReverse" to keep docs links on the top of the mappings code block on smaller screen + + + + + + + + + +

    + +

    +
    +
    +
    + + +

    + +

    +
    + + + + +
    + {extensionsService.indexMappingsContent && ( + <> + + {extensionsService.indexMappingsContent.renderContent({ index, getUrlForApp })} + + )} +
    + + + + + {JSON.stringify(data, null, 2)} + + + +
    + ); +}; diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_summary.tsx b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_summary.tsx index 20a7c172d674b2..492c6f426acd53 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_summary.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_summary.tsx @@ -20,6 +20,8 @@ import { EuiCodeBlock, EuiSpacer, } from '@elastic/eui'; +import { serializeAsESLifecycle } from '../../../../../../../common/lib/data_stream_serialization'; +import { getLifecycleValue } from '../../../../../lib/data_streams'; import { TemplateDeserialized } from '../../../../../../../common'; import { ILM_PAGES_POLICY_EDIT } from '../../../../../constants'; import { useIlmLocator } from '../../../../../services/use_ilm_locator'; @@ -28,6 +30,7 @@ interface Props { templateDetails: TemplateDeserialized; } +const INFINITE_AS_ICON = true; const i18nTexts = { yes: i18n.translate('xpack.idxMgmt.templateDetails.summaryTab.yesDescriptionText', { defaultMessage: 'Yes', @@ -50,6 +53,7 @@ export const TabSummary: React.FunctionComponent = ({ templateDetails }) ilmPolicy, _meta, _kbnMeta: { isLegacy, hasDatastream }, + allowAutoCreate, } = templateDetails; const numIndexPatterns = indexPatterns.length; @@ -192,6 +196,39 @@ export const TabSummary: React.FunctionComponent = ({ templateDetails }) {version || version === 0 ? version : i18nTexts.none} + + {/* Data retention */} + {hasDatastream && templateDetails?.lifecycle && ( + <> + + + + + {getLifecycleValue( + serializeAsESLifecycle(templateDetails.lifecycle), + INFINITE_AS_ICON + )} + + + )} + + {/* Allow auto create */} + {isLegacy !== true && ( + <> + + + + + {allowAutoCreate ? i18nTexts.yes : i18nTexts.no} + + + )} diff --git a/x-pack/plugins/index_management/public/index.ts b/x-pack/plugins/index_management/public/index.ts index b8cde1fdf36e78..8fb836ba7ffd9d 100644 --- a/x-pack/plugins/index_management/public/index.ts +++ b/x-pack/plugins/index_management/public/index.ts @@ -14,7 +14,7 @@ export const plugin = (ctx: PluginInitializerContext) => { return new IndexMgmtUIPlugin(ctx); }; -export type { IndexManagementPluginSetup } from './types'; +export type { IndexManagementPluginSetup, IndexManagementPluginStart } from './types'; export { getIndexListUri, getTemplateDetailsLink } from './application/services/routing'; diff --git a/x-pack/plugins/index_management/public/plugin.ts b/x-pack/plugins/index_management/public/plugin.ts index 87b4ff5be220ca..f4038a4e2677f6 100644 --- a/x-pack/plugins/index_management/public/plugin.ts +++ b/x-pack/plugins/index_management/public/plugin.ts @@ -81,6 +81,10 @@ export class IndexMgmtUIPlugin { }; } - public start() {} + public start() { + return { + extensionsService: this.extensionsService.setup(), + }; + } public stop() {} } diff --git a/x-pack/plugins/index_management/public/services/extensions_service.mock.ts b/x-pack/plugins/index_management/public/services/extensions_service.mock.ts index 3c0886b0fe4a34..8f4968ad35e410 100644 --- a/x-pack/plugins/index_management/public/services/extensions_service.mock.ts +++ b/x-pack/plugins/index_management/public/services/extensions_service.mock.ts @@ -18,6 +18,7 @@ const createServiceMock = (): ExtensionsSetupMock => ({ addToggle: jest.fn(), addIndexDetailsTab: jest.fn(), setIndexOverviewContent: jest.fn(), + setIndexMappingsContent: jest.fn(), }); const createMock = () => { 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 94211b32dbd703..1eb68e9a0b7465 100644 --- a/x-pack/plugins/index_management/public/services/extensions_service.ts +++ b/x-pack/plugins/index_management/public/services/extensions_service.ts @@ -12,7 +12,7 @@ import { EuiBadgeProps } from '@elastic/eui'; import type { IndexDetailsTab } from '../../common/constants'; import { Index } from '..'; -export interface IndexOverviewContent { +export interface IndexContent { renderContent: (args: { index: Index; getUrlForApp: ApplicationStart['getUrlForApp']; @@ -28,13 +28,22 @@ export interface IndexBadge { } export interface ExtensionsSetup { + // adds an option to the "manage index" menu addAction(action: any): void; + // adds a banner to the indices list addBanner(banner: any): void; + // adds a filter to the indices list addFilter(filter: any): void; + // adds a badge to the index name addBadge(badge: IndexBadge): void; + // adds a toggle to the indices list addToggle(toggle: any): void; + // adds a tab to the index details page addIndexDetailsTab(tab: IndexDetailsTab): void; - setIndexOverviewContent(content: IndexOverviewContent): void; + // sets content to render instead of the code block on the overview tab of the index page + setIndexOverviewContent(content: IndexContent): void; + // sets content to render below the docs link on the mappings tab of the index page + setIndexMappingsContent(content: IndexContent): void; } export class ExtensionsService { @@ -55,7 +64,8 @@ export class ExtensionsService { ]; private _toggles: any[] = []; private _indexDetailsTabs: IndexDetailsTab[] = []; - private _indexOverviewContent: IndexOverviewContent | null = null; + private _indexOverviewContent: IndexContent | null = null; + private _indexMappingsContent: IndexContent | null = null; private service?: ExtensionsSetup; public setup(): ExtensionsSetup { @@ -66,7 +76,8 @@ export class ExtensionsService { addFilter: this.addFilter.bind(this), addToggle: this.addToggle.bind(this), addIndexDetailsTab: this.addIndexDetailsTab.bind(this), - setIndexOverviewContent: this.setIndexOverviewMainContent.bind(this), + setIndexOverviewContent: this.setIndexOverviewContent.bind(this), + setIndexMappingsContent: this.setIndexMappingsContent.bind(this), }; return this.service; @@ -96,7 +107,7 @@ export class ExtensionsService { this._indexDetailsTabs.push(tab); } - private setIndexOverviewMainContent(content: IndexOverviewContent) { + private setIndexOverviewContent(content: IndexContent) { if (this._indexOverviewContent) { throw new Error(`The content for index overview has already been set.`); } else { @@ -104,6 +115,14 @@ export class ExtensionsService { } } + private setIndexMappingsContent(content: IndexContent) { + if (this._indexMappingsContent) { + throw new Error(`The content for index mappings has already been set.`); + } else { + this._indexMappingsContent = content; + } + } + public get actions() { return this._actions; } @@ -131,4 +150,8 @@ export class ExtensionsService { public get indexOverviewContent() { return this._indexOverviewContent; } + + public get indexMappingsContent() { + return this._indexMappingsContent; + } } diff --git a/x-pack/plugins/index_management/public/services/index.ts b/x-pack/plugins/index_management/public/services/index.ts index 8f4ddbeffba352..bca35e09c9776b 100644 --- a/x-pack/plugins/index_management/public/services/index.ts +++ b/x-pack/plugins/index_management/public/services/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -export type { ExtensionsSetup } from './extensions_service'; +export type { ExtensionsSetup, IndexContent } from './extensions_service'; export { ExtensionsService } from './extensions_service'; export type { PublicApiServiceSetup } from './public_api_service'; diff --git a/x-pack/plugins/index_management/public/types.ts b/x-pack/plugins/index_management/public/types.ts index 57ddf10c767fdd..d00e6ba4d5ac7c 100644 --- a/x-pack/plugins/index_management/public/types.ts +++ b/x-pack/plugins/index_management/public/types.ts @@ -16,6 +16,10 @@ export interface IndexManagementPluginSetup { extensionsService: ExtensionsSetup; } +export interface IndexManagementPluginStart { + extensionsService: ExtensionsSetup; +} + export interface SetupDependencies { fleet?: unknown; usageCollection: UsageCollectionSetup; diff --git a/x-pack/plugins/index_management/server/routes/api/component_templates/register_get_route.ts b/x-pack/plugins/index_management/server/routes/api/component_templates/register_get_route.ts index 73224c0356ad59..2ccc3919e377aa 100644 --- a/x-pack/plugins/index_management/server/routes/api/component_templates/register_get_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/component_templates/register_get_route.ts @@ -33,9 +33,9 @@ export function registerGetAllRoute({ router, lib: { handleEsError } }: RouteDep const { index_templates: indexTemplates } = await client.asCurrentUser.indices.getIndexTemplate(); - const body = componentTemplates.map((componentTemplate: ComponentTemplateFromEs) => { + const body = componentTemplates.map((componentTemplate) => { const deserializedComponentTemplateListItem = deserializeComponentTemplateList( - componentTemplate, + componentTemplate as ComponentTemplateFromEs, // @ts-expect-error TemplateSerialized.index_patterns not compatible with IndicesIndexTemplate.index_patterns indexTemplates ); diff --git a/x-pack/plugins/index_management/server/routes/api/component_templates/schema_validation.ts b/x-pack/plugins/index_management/server/routes/api/component_templates/schema_validation.ts index c436c70ba7a46d..586e9db9110283 100644 --- a/x-pack/plugins/index_management/server/routes/api/component_templates/schema_validation.ts +++ b/x-pack/plugins/index_management/server/routes/api/component_templates/schema_validation.ts @@ -13,6 +13,12 @@ export const componentTemplateSchema = schema.object({ settings: schema.maybe(schema.object({}, { unknowns: 'allow' })), aliases: schema.maybe(schema.object({}, { unknowns: 'allow' })), mappings: schema.maybe(schema.object({}, { unknowns: 'allow' })), + lifecycle: schema.maybe( + schema.object({ + enabled: schema.boolean(), + data_retention: schema.maybe(schema.string()), + }) + ), }), version: schema.maybe(schema.number()), _meta: schema.maybe(schema.object({}, { unknowns: 'allow' })), diff --git a/x-pack/plugins/index_management/server/routes/api/templates/validate_schemas.ts b/x-pack/plugins/index_management/server/routes/api/templates/validate_schemas.ts index 247eda7206fb87..b9020585ed676e 100644 --- a/x-pack/plugins/index_management/server/routes/api/templates/validate_schemas.ts +++ b/x-pack/plugins/index_management/server/routes/api/templates/validate_schemas.ts @@ -13,11 +13,18 @@ export const templateSchema = schema.object({ version: schema.maybe(schema.number()), order: schema.maybe(schema.number()), priority: schema.maybe(schema.number()), + allowAutoCreate: schema.maybe(schema.boolean()), template: schema.maybe( schema.object({ settings: schema.maybe(schema.object({}, { unknowns: 'allow' })), aliases: schema.maybe(schema.object({}, { unknowns: 'allow' })), mappings: schema.maybe(schema.object({}, { unknowns: 'allow' })), + lifecycle: schema.maybe( + schema.object({ + enabled: schema.boolean(), + data_retention: schema.maybe(schema.string()), + }) + ), }) ), composedOf: schema.maybe(schema.arrayOf(schema.string())), diff --git a/x-pack/plugins/index_management/test/fixtures/template.ts b/x-pack/plugins/index_management/test/fixtures/template.ts index ae9be960157350..01f83e2d76ff51 100644 --- a/x-pack/plugins/index_management/test/fixtures/template.ts +++ b/x-pack/plugins/index_management/test/fixtures/template.ts @@ -7,6 +7,7 @@ import { getRandomString, getRandomNumber } from '@kbn/test-jest-helpers'; import { TemplateDeserialized, TemplateType, TemplateListItem } from '../../common'; +import { IndexSettings, Aliases, Mappings, DataStream } from '../../common/types'; const objHasProperties = (obj?: Record): boolean => { return obj === undefined || Object.keys(obj).length === 0 ? false : true; @@ -17,15 +18,22 @@ export const getComposableTemplate = ({ version = getRandomNumber(), priority = getRandomNumber(), indexPatterns = [], - template: { settings, aliases, mappings } = {}, + template: { settings, aliases, mappings, lifecycle } = {}, hasDatastream = false, isLegacy = false, type = 'default', + allowAutoCreate = false, }: Partial< TemplateDeserialized & { isLegacy?: boolean; type?: TemplateType; hasDatastream: boolean; + template?: { + settings?: IndexSettings; + aliases?: Aliases; + mappings?: Mappings; + lifecycle?: DataStream['lifecycle']; + }; } > = {}): TemplateDeserialized => { const indexTemplate = { @@ -33,10 +41,12 @@ export const getComposableTemplate = ({ version, priority, indexPatterns, + allowAutoCreate, template: { aliases, mappings, settings, + lifecycle, }, _kbnMeta: { type, @@ -58,6 +68,7 @@ export const getTemplate = ({ hasDatastream = false, isLegacy = false, type = 'default', + allowAutoCreate = false, }: Partial< TemplateDeserialized & { isLegacy?: boolean; @@ -70,6 +81,7 @@ export const getTemplate = ({ version, order, indexPatterns, + allowAutoCreate, template: { aliases, mappings, diff --git a/x-pack/plugins/infra/kibana.jsonc b/x-pack/plugins/infra/kibana.jsonc index debd656bddeed0..b97a273b91f716 100644 --- a/x-pack/plugins/infra/kibana.jsonc +++ b/x-pack/plugins/infra/kibana.jsonc @@ -1,7 +1,7 @@ { "type": "plugin", "id": "@kbn/infra-plugin", - "owner": "@elastic/infra-monitoring-ui", + "owner": ["@elastic/infra-monitoring-ui", "@elastic/obs-ux-logs-team", "@elastic/obs-ux-infra_services-team"], "description": "This plugin visualizes data from Filebeat and Metricbeat, and integrates with other Observability solutions", "plugin": { "id": "infra", diff --git a/x-pack/plugins/infra/public/alerting/inventory/components/alert_flyout.tsx b/x-pack/plugins/infra/public/alerting/inventory/components/alert_flyout.tsx index 41fdc1b6f7d30e..d3f564818f03e7 100644 --- a/x-pack/plugins/infra/public/alerting/inventory/components/alert_flyout.tsx +++ b/x-pack/plugins/infra/public/alerting/inventory/components/alert_flyout.tsx @@ -41,6 +41,7 @@ export const AlertFlyout = ({ options, nodeType, filter, visible, setVisible }: filter, customMetrics, }, + useRuleProducer: true, }), // eslint-disable-next-line react-hooks/exhaustive-deps [triggersActionsUI, visible] diff --git a/x-pack/plugins/infra/public/alerting/log_threshold/components/alert_details_app_section/components/log_rate_analysis.tsx b/x-pack/plugins/infra/public/alerting/log_threshold/components/alert_details_app_section/components/log_rate_analysis.tsx index b9ff53140b02c5..a8ebb5c884b6bd 100644 --- a/x-pack/plugins/infra/public/alerting/log_threshold/components/alert_details_app_section/components/log_rate_analysis.tsx +++ b/x-pack/plugins/infra/public/alerting/log_threshold/components/alert_details_app_section/components/log_rate_analysis.tsx @@ -162,7 +162,7 @@ export const LogRateAnalysis: FC = ({ r const onAnalysisCompleted = (analysisResults: LogRateAnalysisResultsData | undefined) => { const significantFieldValues = orderBy( - analysisResults?.significantTerms?.map((item) => ({ + analysisResults?.significantItems?.map((item) => ({ field: item.fieldName, value: item.fieldValue, docCount: item.doc_count, diff --git a/x-pack/plugins/infra/public/components/asset_details/__stories__/context/fixtures/anomalies.ts b/x-pack/plugins/infra/public/components/asset_details/__stories__/context/fixtures/anomalies.ts index b49e14850f7b6b..543b46ceb17b90 100644 --- a/x-pack/plugins/infra/public/components/asset_details/__stories__/context/fixtures/anomalies.ts +++ b/x-pack/plugins/infra/public/components/asset_details/__stories__/context/fixtures/anomalies.ts @@ -16,7 +16,7 @@ const anomalies: GetMetricsHostsAnomaliesSuccessResponsePayload = { actual: 758.8220213274412, anomalyScore: 0.024881740359975164, duration: 900, - startTime: 1486845000000, + startTime: 1681038638000, type: 'metrics_hosts', partitionFieldName: 'airline', partitionFieldValue: 'NKS', @@ -42,7 +42,7 @@ const anomalies: GetMetricsHostsAnomaliesSuccessResponsePayload = { actual: 758.8220213274412, anomalyScore: 100.024881740359975164, duration: 900, - startTime: 1486845000000, + startTime: 1681038638000, type: 'metrics_hosts', partitionFieldName: 'airline', partitionFieldValue: 'NKS', diff --git a/x-pack/plugins/infra/public/components/asset_details/__stories__/context/fixtures/asset_details_props.ts b/x-pack/plugins/infra/public/components/asset_details/__stories__/context/fixtures/asset_details_props.ts index 601c064471078d..9d6b4efb96ec26 100644 --- a/x-pack/plugins/infra/public/components/asset_details/__stories__/context/fixtures/asset_details_props.ts +++ b/x-pack/plugins/infra/public/components/asset_details/__stories__/context/fixtures/asset_details_props.ts @@ -16,12 +16,6 @@ const tabs: Tab[] = [ defaultMessage: 'Overview', }), }, - { - id: ContentTabIds.LOGS, - name: i18n.translate('xpack.infra.nodeDetails.tabs.logs', { - defaultMessage: 'Logs', - }), - }, { id: ContentTabIds.METADATA, name: i18n.translate('xpack.infra.metrics.nodeDetails.tabs.metadata', { @@ -34,6 +28,12 @@ const tabs: Tab[] = [ defaultMessage: 'Processes', }), }, + { + id: ContentTabIds.LOGS, + name: i18n.translate('xpack.infra.nodeDetails.tabs.logs', { + defaultMessage: 'Logs', + }), + }, { id: ContentTabIds.ANOMALIES, name: i18n.translate('xpack.infra.nodeDetails.tabs.anomalies', { diff --git a/x-pack/plugins/infra/public/components/asset_details/__stories__/decorator.tsx b/x-pack/plugins/infra/public/components/asset_details/__stories__/decorator.tsx index 11401bd256feb7..e779808347ec46 100644 --- a/x-pack/plugins/infra/public/components/asset_details/__stories__/decorator.tsx +++ b/x-pack/plugins/infra/public/components/asset_details/__stories__/decorator.tsx @@ -11,24 +11,33 @@ import { KibanaContextProvider, type KibanaReactContextValue, } from '@kbn/kibana-react-plugin/public'; -import { of } from 'rxjs'; +import { Observable, of } from 'rxjs'; import { action } from '@storybook/addon-actions'; import type { DecoratorFn } from '@storybook/react'; import { useParameter } from '@storybook/addons'; import type { DeepPartial } from 'utility-types'; import type { LocatorPublic } from '@kbn/share-plugin/public'; -import type { IKibanaSearchRequest, ISearchOptions } from '@kbn/data-plugin/public'; +import type { + IKibanaSearchRequest, + ISearchOptions, + SearchSessionState, +} from '@kbn/data-plugin/public'; import { AlertSummaryWidget } from '@kbn/triggers-actions-ui-plugin/public/application/sections/alert_summary_widget/alert_summary_widget'; import type { Theme } from '@elastic/charts/dist/utils/themes/theme'; import type { AlertSummaryWidgetProps } from '@kbn/triggers-actions-ui-plugin/public/application/sections/alert_summary_widget'; import { defaultLogViewAttributes } from '@kbn/logs-shared-plugin/common'; import { DataView, DataViewField } from '@kbn/data-views-plugin/common'; +import { MemoryRouter } from 'react-router-dom'; +import { ObservabilityAIAssistantProvider } from '@kbn/observability-ai-assistant-plugin/public'; +import { ObservabilityAIAssistantService } from '@kbn/observability-ai-assistant-plugin/public/types'; +import { PluginConfigProvider } from '../../../containers/plugin_config_context'; import type { PluginKibanaContextValue } from '../../../hooks/use_kibana'; import { SourceProvider } from '../../../containers/metrics_source'; import { getHttp } from './context/http'; import { assetDetailsProps, getLogEntries } from './context/fixtures'; import { ContextProviders } from '../context_providers'; import { DataViewsProvider } from '../hooks/use_data_views'; +import type { InfraConfig } from '../../../../server'; const settings: Record = { 'dateFormat:scaled': [['', 'HH:mm:ss.SSS']], @@ -58,6 +67,10 @@ export const DecorateWithKibanaContext: DecoratorFn = (story) => { search: (request: IKibanaSearchRequest, options?: ISearchOptions) => { return getLogEntries(request, options) as any; }, + session: { + start: () => 'started', + state$: { closed: false } as unknown as Observable, + }, }, query: { filterManager: { @@ -144,14 +157,64 @@ export const DecorateWithKibanaContext: DecoratorFn = (story) => { }, telemetry: { reportAssetDetailsFlyoutViewed: () => {}, + reportAssetDetailsPageViewed: () => {}, + }, + }; + + const config: InfraConfig = { + alerting: { + inventory_threshold: { + group_by_page_size: 11, + }, + metric_threshold: { + group_by_page_size: 11, + }, + }, + enabled: true, + inventory: { + compositeSize: 11, + }, + sources: { + default: { + fields: { + message: ['default'], + }, + }, + }, + featureFlags: { + customThresholdAlertsEnabled: true, + logsUIEnabled: false, + metricsExplorerEnabled: false, + osqueryEnabled: true, + inventoryThresholdAlertRuleEnabled: true, + metricThresholdAlertRuleEnabled: true, + logThresholdAlertRuleEnabled: true, + alertsAndRulesDropdownEnabled: true, }, }; return ( - - {story()} - + + + + true, + callApi: () => {}, + getCurrentUser: () => {}, + getLicense: () => {}, + getLicenseManagementLocator: () => {}, + start: {}, + } as unknown as ObservabilityAIAssistantService + } + > + {story()} + + + + ); }; diff --git a/x-pack/plugins/infra/public/components/asset_details/asset_details.stories.tsx b/x-pack/plugins/infra/public/components/asset_details/asset_details.stories.tsx index 0816e25dc800b6..c85677dbace941 100644 --- a/x-pack/plugins/infra/public/components/asset_details/asset_details.stories.tsx +++ b/x-pack/plugins/infra/public/components/asset_details/asset_details.stories.tsx @@ -6,19 +6,32 @@ */ import React, { useState } from 'react'; -import { EuiButton } from '@elastic/eui'; +import { EuiButton, EuiCallOut, EuiSelect, EuiSpacer } from '@elastic/eui'; import type { Meta, Story } from '@storybook/react/types-6-0'; +import { MemoryRouter } from 'react-router-dom'; +import { useArgs } from '@storybook/addons'; import { AssetDetails } from './asset_details'; import { decorateWithGlobalStorybookThemeProviders } from '../../test_utils/use_global_storybook_theme'; -import { type AssetDetailsProps } from './types'; +import { type TabIds, type AssetDetailsProps } from './types'; import { DecorateWithKibanaContext } from './__stories__/decorator'; import { assetDetailsProps } from './__stories__/context/fixtures'; -const stories: Meta = { +interface AssetDetailsStoryArgs extends AssetDetailsProps { + tabId: TabIds; +} + +const stories: Meta = { title: 'infra/Asset Details View', decorators: [decorateWithGlobalStorybookThemeProviders, DecorateWithKibanaContext], component: AssetDetails, argTypes: { + tabId: { + options: assetDetailsProps.tabs.filter(({ id }) => id !== 'linkToApm').map(({ id }) => id), + defaultValue: 'overview', + control: { + type: 'radio', + }, + }, links: { options: assetDetailsProps.links, control: { @@ -26,20 +39,42 @@ const stories: Meta = { }, }, }, - args: { - ...assetDetailsProps, - }, + args: { ...assetDetailsProps }, }; -const PageTemplate: Story = (args) => { - return ; +const PageTabTemplate: Story = (args) => { + return ( + + + + ); }; -const FlyoutTemplate: Story = (args) => { +const FlyoutTemplate: Story = (args) => { const [isOpen, setIsOpen] = useState(false); const closeFlyout = () => setIsOpen(false); + const options = assetDetailsProps.tabs.filter(({ id }) => id !== 'linkToApm').map(({ id }) => id); + const [{ tabId }, updateArgs] = useArgs(); + return (
    + + + { + updateArgs({ tabId: e.target.value as TabIds }); + }} + options={options.map((id) => ({ + text: id, + value: id, + }))} + /> + setIsOpen(true)} @@ -47,13 +82,33 @@ const FlyoutTemplate: Story = (args) => { Open flyout
    ); }; -export const Page = PageTemplate.bind({}); +export const OverviewTab = PageTabTemplate.bind({}); +OverviewTab.args = { tabId: 'overview' }; + +export const MetadataTab = PageTabTemplate.bind({}); +MetadataTab.args = { tabId: 'metadata' }; + +export const ProcessesTab = PageTabTemplate.bind({}); +ProcessesTab.args = { tabId: 'processes' }; + +export const LogsTab = PageTabTemplate.bind({}); +LogsTab.args = { tabId: 'logs' }; + +export const AnomaliesTab = PageTabTemplate.bind({}); +AnomaliesTab.args = { tabId: 'anomalies' }; export const Flyout = FlyoutTemplate.bind({}); diff --git a/x-pack/plugins/infra/public/components/asset_details/tabs/anomalies/anomalies.stories.tsx b/x-pack/plugins/infra/public/components/asset_details/tabs/anomalies/anomalies.stories.tsx deleted file mode 100644 index 25db2ae7445d64..00000000000000 --- a/x-pack/plugins/infra/public/components/asset_details/tabs/anomalies/anomalies.stories.tsx +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import type { Meta, Story } from '@storybook/react/types-6-0'; - -import { Anomalies } from './anomalies'; -import { decorateWithGlobalStorybookThemeProviders } from '../../../../test_utils/use_global_storybook_theme'; -import { - DecorateWithKibanaContext, - DecorateWithAssetDetailsStateContext, -} from '../../__stories__/decorator'; - -const stories: Meta = { - title: 'infra/Asset Details View/Components/Anomalies', - decorators: [ - decorateWithGlobalStorybookThemeProviders, - DecorateWithKibanaContext, - DecorateWithAssetDetailsStateContext, - ], - component: Anomalies, -}; - -const Template: Story = () => { - return ; -}; - -export const Default = Template.bind({}); - -export const NoData = Template.bind({}); -NoData.parameters = { - apiResponse: { - mock: 'noData', - }, -}; - -export const LoadingState = Template.bind({}); -LoadingState.parameters = { - apiResponse: { - mock: 'loading', - }, -}; - -export default stories; diff --git a/x-pack/plugins/infra/public/components/asset_details/tabs/metadata/metadata.stories.tsx b/x-pack/plugins/infra/public/components/asset_details/tabs/metadata/metadata.stories.tsx index 4a48b63f73d41b..323d3302589d96 100644 --- a/x-pack/plugins/infra/public/components/asset_details/tabs/metadata/metadata.stories.tsx +++ b/x-pack/plugins/infra/public/components/asset_details/tabs/metadata/metadata.stories.tsx @@ -18,8 +18,8 @@ const stories: Meta = { title: 'infra/Asset Details View/Components/Metadata', decorators: [ decorateWithGlobalStorybookThemeProviders, - DecorateWithKibanaContext, DecorateWithAssetDetailsStateContext, + DecorateWithKibanaContext, ], component: Metadata, }; @@ -30,11 +30,6 @@ const Template: Story = () => { export const Default = Template.bind({}); -export const WithActions = Template.bind({}); -WithActions.args = { - showActionsColumn: true, -}; - export const NoData = Template.bind({}); NoData.parameters = { apiResponse: { diff --git a/x-pack/plugins/infra/public/components/asset_details/tabs/processes/processes.stories.tsx b/x-pack/plugins/infra/public/components/asset_details/tabs/processes/processes.stories.tsx index feb055a6a14e0d..94e153d1f05899 100644 --- a/x-pack/plugins/infra/public/components/asset_details/tabs/processes/processes.stories.tsx +++ b/x-pack/plugins/infra/public/components/asset_details/tabs/processes/processes.stories.tsx @@ -18,8 +18,8 @@ const stories: Meta = { title: 'infra/Asset Details View/Components/Processes', decorators: [ decorateWithGlobalStorybookThemeProviders, - DecorateWithKibanaContext, DecorateWithAssetDetailsStateContext, + DecorateWithKibanaContext, ], component: Processes, }; diff --git a/x-pack/plugins/infra/public/hooks/use_http_request.tsx b/x-pack/plugins/infra/public/hooks/use_http_request.tsx index 7e2b6f03ab9166..81966770047146 100644 --- a/x-pack/plugins/infra/public/hooks/use_http_request.tsx +++ b/x-pack/plugins/infra/public/hooks/use_http_request.tsx @@ -7,8 +7,7 @@ import React, { useCallback, useEffect, useRef, useState } from 'react'; import { i18n } from '@kbn/i18n'; -import { HttpHandler } from '@kbn/core/public'; -import { ToastInput } from '@kbn/core/public'; +import { HttpHandler, ToastInput } from '@kbn/core/public'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { AbortError } from '@kbn/kibana-utils-plugin/common'; import { useTrackedPromise, CanceledPromiseError } from '../utils/use_tracked_promise'; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/snapshot_container.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/snapshot_container.tsx index b22bcb59a3c129..40b41ac3a1d5e6 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/snapshot_container.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/snapshot_container.tsx @@ -25,7 +25,7 @@ interface Props { } export const SnapshotContainer = ({ render }: Props) => { const { sourceId } = useSourceContext(); - const { metric, groupBy, nodeType, accountId, region } = useWaffleOptionsContext(); + const { metric, groupBy, nodeType, accountId, region, view } = useWaffleOptionsContext(); const { currentTime } = useWaffleTimeContext(); const { filterQueryAsJson } = useWaffleFiltersContext(); const { @@ -33,17 +33,23 @@ export const SnapshotContainer = ({ render }: Props) => { nodes, reload, interval = '60s', - } = useSnapshot({ - filterQuery: filterQueryAsJson, - metrics: [metric], - groupBy, - nodeType, - sourceId, - currentTime, - accountId, - region, - sendRequestImmediately: false, - }); + } = useSnapshot( + { + filterQuery: filterQueryAsJson, + metrics: [metric], + groupBy, + nodeType, + sourceId, + currentTime, + accountId, + region, + sendRequestImmediately: false, + includeTimeseries: view === 'table', + }, + { + abortable: true, + } + ); return render({ loading, nodes, reload, interval }); }; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/__snapshots__/conditional_tooltip.test.tsx.snap b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/__snapshots__/conditional_tooltip.test.tsx.snap index 02c8f6aa6ed3b9..f3368be815f1b3 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/__snapshots__/conditional_tooltip.test.tsx.snap +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/__snapshots__/conditional_tooltip.test.tsx.snap @@ -6,7 +6,7 @@ exports[`ConditionalToolTip renders correctly 1`] = ` style="min-width: 200px;" >
    host-01
    diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/conditional_tooltip.test.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/conditional_tooltip.test.tsx index 66b8ff975ba300..f023cc1c5760ca 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/conditional_tooltip.test.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/conditional_tooltip.test.tsx @@ -7,7 +7,6 @@ import React from 'react'; import { mount } from 'enzyme'; -import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; import { ConditionalToolTip } from './conditional_tooltip'; import { SnapshotNodeResponse } from '../../../../../../common/http_api'; import { InfraWaffleMapNode } from '../../../../../lib/lib'; @@ -98,23 +97,25 @@ describe('ConditionalToolTip', () => { }, ]; const wrapper = mount( - - - + ); const tooltip = wrapper.find('[data-test-subj~="conditionalTooltipContent-host-01"]'); expect(tooltip.render()).toMatchSnapshot(); - expect(mockedUseSnapshot).toBeCalledWith({ - filterQuery: expectedQuery, - metrics: expectedMetrics, - groupBy: [], - nodeType: 'host', - sourceId: 'default', - currentTime, - accountId: '', - region: '', - } as UseSnapshotRequest); + expect(mockedUseSnapshot).toBeCalledWith( + { + filterQuery: expectedQuery, + metrics: expectedMetrics, + groupBy: [], + includeTimeseries: false, + nodeType: 'host', + sourceId: 'default', + currentTime, + accountId: '', + region: '', + } as UseSnapshotRequest, + { abortable: true } + ); }); }); diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/conditional_tooltip.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/conditional_tooltip.tsx index 4d82f7b7a39c8d..7bbb696103cd65 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/conditional_tooltip.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/conditional_tooltip.tsx @@ -5,10 +5,9 @@ * 2.0. */ -import React from 'react'; -import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import React, { useRef } from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner, useEuiTheme } from '@elastic/eui'; import { first } from 'lodash'; -import { withTheme, EuiTheme } from '@kbn/kibana-react-plugin/common'; import { findInventoryModel } from '@kbn/metrics-data-access-plugin/common'; import { InventoryItemType, @@ -29,10 +28,13 @@ export interface Props { currentTime: number; node: InfraWaffleMapNode; nodeType: InventoryItemType; - theme: EuiTheme | undefined; } -export const ConditionalToolTip = withTheme(({ theme, node, nodeType, currentTime }: Props) => { + +export const ConditionalToolTip = ({ node, nodeType, currentTime }: Props) => { + const { euiTheme } = useEuiTheme(); const { sourceId } = useSourceContext(); + // prevents auto-refresh from cancelling ongoing requests to fetch the data for the tooltip + const requestCurrentTime = useRef(currentTime); const model = findInventoryModel(nodeType); const { customMetrics } = useWaffleOptionsContext(); const requestMetrics = model.tooltipMetrics @@ -50,16 +52,22 @@ export const ConditionalToolTip = withTheme(({ theme, node, nodeType, currentTim }, }, }); - const { nodes } = useSnapshot({ - filterQuery: query, - metrics: requestMetrics, - groupBy: [], - nodeType, - sourceId, - currentTime, - accountId: '', - region: '', - }); + const { nodes, loading } = useSnapshot( + { + filterQuery: query, + metrics: requestMetrics, + groupBy: [], + nodeType, + sourceId, + currentTime: requestCurrentTime.current, + accountId: '', + region: '', + includeTimeseries: false, + }, + { + abortable: true, + } + ); const dataNode = first(nodes); const metrics = (dataNode && dataNode.metrics) || []; @@ -67,38 +75,47 @@ export const ConditionalToolTip = withTheme(({ theme, node, nodeType, currentTim
    {node.name}
    - {metrics.map((metric) => { - const metricName = SnapshotMetricTypeRT.is(metric.name) ? metric.name : 'custom'; - const name = SNAPSHOT_METRIC_TRANSLATIONS[metricName] || metricName; - // if custom metric, find field and label from waffleOptionsContext result - // because useSnapshot does not return it - const customMetric = - name === 'custom' ? customMetrics.find((item) => item.id === metric.name) : null; - const formatter = customMetric - ? createFormatterForMetric(customMetric) - : createInventoryMetricFormatter({ type: metricName }); - return ( - - - {customMetric ? getCustomMetricLabel(customMetric) : name} - - - {(metric.value && formatter(metric.value)) || '-'} - - - ); - })} + {loading ? ( + + + + + + ) : ( + metrics.map((metric) => { + const metricName = SnapshotMetricTypeRT.is(metric.name) ? metric.name : 'custom'; + const name = SNAPSHOT_METRIC_TRANSLATIONS[metricName] || metricName; + // if custom metric, find field and label from waffleOptionsContext result + // because useSnapshot does not return it + const customMetric = + name === 'custom' ? customMetrics.find((item) => item.id === metric.name) : null; + const formatter = customMetric + ? createFormatterForMetric(customMetric) + : createInventoryMetricFormatter({ type: metricName }); + return ( + + + {customMetric ? getCustomMetricLabel(customMetric) : name} + + + {(metric.value && formatter(metric.value)) || '-'} + + + ); + }) + )}
    ); -}); +}; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_timeline.ts b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_timeline.ts index d603972743e0a1..f949ee5f3a87be 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_timeline.ts +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_timeline.ts @@ -5,21 +5,12 @@ * 2.0. */ -import { fold } from 'fp-ts/lib/Either'; -import { identity } from 'fp-ts/lib/function'; -import { pipe } from 'fp-ts/lib/pipeable'; import { first } from 'lodash'; -import { useEffect, useMemo, useCallback } from 'react'; +import { useEffect, useMemo } from 'react'; import type { InventoryItemType, SnapshotMetricType } from '@kbn/metrics-data-access-plugin/common'; import { getIntervalInSeconds } from '../../../../../common/utils/get_interval_in_seconds'; -import { throwErrors, createPlainError } from '../../../../../common/runtime_types'; -import { useHTTPRequest } from '../../../../hooks/use_http_request'; -import { - SnapshotNodeResponseRT, - SnapshotNodeResponse, - SnapshotRequest, - InfraTimerangeInput, -} from '../../../../../common/http_api/snapshot_api'; +import { InfraTimerangeInput } from '../../../../../common/http_api/snapshot_api'; +import { useSnapshot } from './use_snaphot'; const ONE_MINUTE = 60; const ONE_HOUR = ONE_MINUTE * 60; @@ -64,13 +55,6 @@ export function useTimeline( interval: string | undefined, shouldReload: boolean ) { - const decodeResponse = (response: any) => { - return pipe( - SnapshotNodeResponseRT.decode(response), - fold(throwErrors(createPlainError), identity) - ); - }; - const displayInterval = useMemo(() => getDisplayInterval(interval), [interval]); const timeLengthResult = useMemo( @@ -88,12 +72,11 @@ export function useTimeline( forceInterval: true, }; - const { error, loading, response, makeRequest } = useHTTPRequest( - '/api/metrics/snapshot', - 'POST', - JSON.stringify({ + const { nodes, error, loading, reload } = useSnapshot( + { metrics, groupBy: null, + currentTime, nodeType, timerange, filterQuery, @@ -101,33 +84,27 @@ export function useTimeline( accountId, region, includeTimeseries: true, - } as SnapshotRequest), - decodeResponse + sendRequestImmediately: false, + }, + { + abortable: true, + } ); - const loadData = useCallback(() => { - if (shouldReload) return makeRequest(); - return Promise.resolve(); - }, [makeRequest, shouldReload]); - useEffect(() => { (async () => { - if (timeLength) { - await loadData(); - } + if (shouldReload) return reload(); })(); - }, [loadData, timeLength]); + }, [reload, shouldReload]); - const timeseries = response - ? first(response.nodes.map((node) => first(node.metrics)?.timeseries)) - : null; + const timeseries = nodes ? first(nodes.map((node) => first(node.metrics)?.timeseries)) : null; return { - error: (error && error.message) || null, + error: error || null, loading: !interval ? true : loading, timeseries, startTime, endTime, - reload: makeRequest, + reload, }; } diff --git a/x-pack/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts b/x-pack/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts index 9780a7811b8241..9a593af54a0107 100644 --- a/x-pack/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts +++ b/x-pack/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts @@ -26,6 +26,7 @@ import { InfraServerPluginStartDeps, InfraVersionedRouteConfig, } from './adapter_types'; +import { subscribeToAborted$ } from '../../cancel_request_on_abort'; interface FrozenIndexParams { ignore_throttled?: boolean; @@ -115,42 +116,50 @@ export class KibanaFramework { callWithRequest( requestContext: InfraPluginRequestHandlerContext, endpoint: 'search', - options?: CallWithRequestParams + options?: CallWithRequestParams, + request?: KibanaRequest ): Promise>; callWithRequest( requestContext: InfraPluginRequestHandlerContext, endpoint: 'msearch', - options?: CallWithRequestParams + options?: CallWithRequestParams, + request?: KibanaRequest ): Promise>; callWithRequest( requestContext: InfraPluginRequestHandlerContext, endpoint: 'indices.existsAlias', - options?: CallWithRequestParams + options?: CallWithRequestParams, + request?: KibanaRequest ): Promise; callWithRequest( requestContext: InfraPluginRequestHandlerContext, method: 'indices.getAlias', - options?: object + options?: object, + request?: KibanaRequest ): Promise; callWithRequest( requestContext: InfraPluginRequestHandlerContext, method: 'indices.get' | 'ml.getBuckets', - options?: object + options?: object, + request?: KibanaRequest ): Promise; callWithRequest( requestContext: InfraPluginRequestHandlerContext, method: 'transport.request', - options?: CallWithRequestParams + options?: CallWithRequestParams, + request?: KibanaRequest ): Promise; callWithRequest( requestContext: InfraPluginRequestHandlerContext, endpoint: string, - options?: CallWithRequestParams + options?: CallWithRequestParams, + request?: KibanaRequest ): Promise; public async callWithRequest( requestContext: InfraPluginRequestHandlerContext, endpoint: string, - params: CallWithRequestParams + params: CallWithRequestParams, + request?: KibanaRequest ) { const { elasticsearch, uiSettings } = await requestContext.core; @@ -164,6 +173,16 @@ export class KibanaFramework { } } + function callWrapper({ + makeRequestWithSignal, + }: { + makeRequestWithSignal: (signal: AbortSignal) => Promise; + }) { + const controller = new AbortController(); + const promise = makeRequestWithSignal(controller.signal); + return request ? subscribeToAborted$(promise, request, controller) : promise; + } + // Only set the "ignore_throttled" value (to false) if the Kibana setting // for "search:includeFrozen" is true (i.e. don't ignore throttled indices, a triple negative!) // More information: @@ -179,41 +198,90 @@ export class KibanaFramework { let apiResult; switch (endpoint) { case 'search': - apiResult = elasticsearch.client.asCurrentUser.search({ - ...params, - ...frozenIndicesParams, + apiResult = callWrapper({ + makeRequestWithSignal: (signal) => + elasticsearch.client.asCurrentUser.search( + { + ...params, + ...frozenIndicesParams, + } as estypes.MsearchRequest, + { signal } + ), }); + break; case 'msearch': - apiResult = elasticsearch.client.asCurrentUser.msearch({ - ...params, - ...frozenIndicesParams, - } as estypes.MsearchRequest); + apiResult = callWrapper({ + makeRequestWithSignal: (signal) => + elasticsearch.client.asCurrentUser.msearch( + { + ...params, + ...frozenIndicesParams, + } as estypes.MsearchRequest, + { signal } + ), + }); + break; case 'indices.existsAlias': - apiResult = elasticsearch.client.asCurrentUser.indices.existsAlias({ - ...params, - } as estypes.IndicesExistsAliasRequest); + apiResult = callWrapper({ + makeRequestWithSignal: (signal) => + elasticsearch.client.asCurrentUser.indices.existsAlias( + { + ...params, + } as estypes.IndicesExistsAliasRequest, + { signal } + ), + }); + break; case 'indices.getAlias': - apiResult = elasticsearch.client.asCurrentUser.indices.getAlias({ - ...params, + apiResult = callWrapper({ + makeRequestWithSignal: (signal) => + elasticsearch.client.asCurrentUser.indices.getAlias( + { + ...params, + }, + { signal } + ), }); + break; case 'indices.get': - apiResult = elasticsearch.client.asCurrentUser.indices.get({ - ...params, - } as estypes.IndicesGetRequest); + apiResult = callWrapper({ + makeRequestWithSignal: (signal) => + elasticsearch.client.asCurrentUser.indices.get( + { + ...params, + } as estypes.IndicesGetRequest, + { signal } + ), + }); + break; case 'transport.request': - apiResult = elasticsearch.client.asCurrentUser.transport.request({ - ...params, - } as TransportRequestParams); + apiResult = callWrapper({ + makeRequestWithSignal: (signal) => + elasticsearch.client.asCurrentUser.transport.request( + { + ...params, + } as TransportRequestParams, + { signal } + ), + }); + break; case 'ml.getBuckets': - apiResult = elasticsearch.client.asCurrentUser.ml.getBuckets({ - ...params, - } as estypes.MlGetBucketsRequest); + apiResult = callWrapper({ + makeRequestWithSignal: (signal) => + elasticsearch.client.asCurrentUser.ml.getBuckets( + { + ...params, + } as estypes.MlGetBucketsRequest, + { signal } + ), + }); + break; } return apiResult ? await apiResult : undefined; diff --git a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.test.ts b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.test.ts index 29c4bfe0a159ac..bd6350ba59dd03 100644 --- a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.test.ts +++ b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.test.ts @@ -90,7 +90,9 @@ const mockOptions = { }; const setEvaluationResults = (response: Record) => { - jest.requireMock('./evaluate_condition').evaluateCondition.mockImplementation(() => response); + return jest + .requireMock('./evaluate_condition') + .evaluateCondition.mockImplementation(() => response); }; const createMockStaticConfiguration = (sources: any) => ({ alerting: { @@ -192,7 +194,7 @@ const baseCriterion = { describe('The inventory threshold alert type', () => { describe('querying with Hosts and rule tags', () => { afterAll(() => clearInstances()); - const execute = (comparator: Comparator, threshold: number[], state?: any) => + const execute = (comparator: Comparator, threshold: number[], options?: any) => executor({ ...mockOptions, services, @@ -206,11 +208,12 @@ describe('The inventory threshold alert type', () => { }, ], }, - state: state ?? {}, + state: {}, rule: { ...mockOptions.rule, tags: ['ruleTag1', 'ruleTag2'], }, + ...options, }); const instanceIdA = 'host-01'; @@ -305,5 +308,39 @@ describe('The inventory threshold alert type', () => { expect(mostRecentAction(instanceIdA).action.tags).toStrictEqual(['ruleTag1', 'ruleTag2']); expect(mostRecentAction(instanceIdB).action.tags).toStrictEqual(['ruleTag1', 'ruleTag2']); }); + + test('should call evaluation query with delay', async () => { + const mockedStartDate = new Date('2023-11-06T10:04:26.465Z'); + const mockedEndDate = new Date('2023-11-06T10:05:26.465Z'); + const options = { + getTimeRange: () => { + return { dateStart: mockedStartDate, dateEnd: mockedEndDate }; + }, + }; + const evaluateConditionFn = setEvaluationResults({ + 'host-01': { + ...baseCriterion, + metric: 'count', + timeSize: 1, + timeUnit: 'm', + threshold: [0.75], + comparator: Comparator.GT, + shouldFire: true, + shouldWarn: false, + currentValue: 1.0, + isNoData: false, + isError: false, + context: { + cloud: undefined, + }, + }, + }); + await execute(Comparator.GT, [0.75], options); + expect(evaluateConditionFn).toHaveBeenCalledWith( + expect.objectContaining({ + executionTimestamp: mockedEndDate, + }) + ); + }); }); }); diff --git a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts index 7e3315510feeca..ce2f66645bf1db 100644 --- a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts +++ b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts @@ -83,6 +83,7 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = spaceId, startedAt, rule: { id: ruleId, tags: ruleTags }, + getTimeRange, }) => { const startTime = Date.now(); @@ -175,13 +176,14 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = ); const compositeSize = libs.configuration.alerting.inventory_threshold.group_by_page_size; + const { dateEnd } = getTimeRange(); const results = await Promise.all( criteria.map((condition) => evaluateCondition({ compositeSize, condition, esClient, - executionTimestamp: startedAt, + executionTimestamp: new Date(dateEnd), filterQuery, logger, logQueryFields, diff --git a/x-pack/plugins/infra/server/lib/cancel_request_on_abort.ts b/x-pack/plugins/infra/server/lib/cancel_request_on_abort.ts new file mode 100644 index 00000000000000..631af6991bd090 --- /dev/null +++ b/x-pack/plugins/infra/server/lib/cancel_request_on_abort.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { KibanaRequest } from '@kbn/core/server'; + +export function subscribeToAborted$>( + promise: T, + request: KibanaRequest, + controller: AbortController +): T { + const subscription = request.events.aborted$.subscribe(() => { + controller.abort(); + }); + + return promise.finally(() => { + subscription.unsubscribe(); + }) as T; +} diff --git a/x-pack/plugins/infra/server/lib/create_search_client.ts b/x-pack/plugins/infra/server/lib/create_search_client.ts index 6688ae1af1afc8..00f89fb3c8e8b0 100644 --- a/x-pack/plugins/infra/server/lib/create_search_client.ts +++ b/x-pack/plugins/infra/server/lib/create_search_client.ts @@ -5,13 +5,18 @@ * 2.0. */ +import type { KibanaRequest } from '@kbn/core/server'; import type { InfraPluginRequestHandlerContext } from '../types'; import { CallWithRequestParams, InfraDatabaseSearchResponse } from './adapters/framework'; import { KibanaFramework } from './adapters/framework/kibana_framework_adapter'; export const createSearchClient = - (requestContext: InfraPluginRequestHandlerContext, framework: KibanaFramework) => + ( + requestContext: InfraPluginRequestHandlerContext, + framework: KibanaFramework, + request?: KibanaRequest + ) => ( opts: CallWithRequestParams ): Promise> => - framework.callWithRequest(requestContext, 'search', opts); + framework.callWithRequest(requestContext, 'search', opts, request); diff --git a/x-pack/plugins/infra/server/plugin.ts b/x-pack/plugins/infra/server/plugin.ts index 6a2734b3991993..afcbe69c01f5c5 100644 --- a/x-pack/plugins/infra/server/plugin.ts +++ b/x-pack/plugins/infra/server/plugin.ts @@ -98,7 +98,7 @@ export const config: PluginConfigDescriptor = { }), inventoryThresholdAlertRuleEnabled: offeringBasedSchema({ traditional: schema.boolean({ defaultValue: true }), - serverless: schema.boolean({ defaultValue: false }), + serverless: schema.boolean({ defaultValue: true }), }), metricThresholdAlertRuleEnabled: offeringBasedSchema({ traditional: schema.boolean({ defaultValue: true }), @@ -110,7 +110,7 @@ export const config: PluginConfigDescriptor = { }), alertsAndRulesDropdownEnabled: offeringBasedSchema({ traditional: schema.boolean({ defaultValue: true }), - serverless: schema.boolean({ defaultValue: false }), + serverless: schema.boolean({ defaultValue: true }), }), }), }), diff --git a/x-pack/plugins/infra/server/routes/snapshot/index.ts b/x-pack/plugins/infra/server/routes/snapshot/index.ts index 085be4584e2a7e..ae1f02a04ca933 100644 --- a/x-pack/plugins/infra/server/routes/snapshot/index.ts +++ b/x-pack/plugins/infra/server/routes/snapshot/index.ts @@ -53,7 +53,7 @@ export const initSnapshotRoute = (libs: InfraBackendLibs) => { ); UsageCollector.countNode(snapshotRequest.nodeType); - const client = createSearchClient(requestContext, framework); + const client = createSearchClient(requestContext, framework, request); try { const snapshotResponse = await getNodes( diff --git a/x-pack/plugins/kubernetes_security/public/components/count_widget/index.test.tsx b/x-pack/plugins/kubernetes_security/public/components/count_widget/index.test.tsx index a0fbe2c90c6909..d3230a33f203eb 100644 --- a/x-pack/plugins/kubernetes_security/public/components/count_widget/index.test.tsx +++ b/x-pack/plugins/kubernetes_security/public/components/count_widget/index.test.tsx @@ -10,7 +10,7 @@ import { AppContextTestRender, createAppRootMockRenderer } from '../../test'; import { GlobalFilter } from '../../types'; import { CountWidget, LOADING_TEST_ID, TOOLTIP_TEST_ID, VALUE_TEST_ID } from '.'; import { useFetchCountWidgetData } from './hooks'; -import { fireEvent, waitFor } from '@testing-library/dom'; +import { fireEvent, waitFor } from '@testing-library/react'; const TITLE = 'Count Widget Title'; const GLOBAL_FILTER: GlobalFilter = { diff --git a/x-pack/plugins/lens/public/app_plugin/app.tsx b/x-pack/plugins/lens/public/app_plugin/app.tsx index d68476598ad992..39a614b568799f 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.tsx @@ -17,7 +17,7 @@ import type { LensAppLocatorParams } from '../../common/locator/locator'; import { LensAppProps, LensAppServices } from './types'; import { LensTopNavMenu } from './lens_top_nav'; import { LensByReferenceInput } from '../embeddable'; -import { AddUserMessages, EditorFrameInstance, UserMessage, UserMessagesGetter } from '../types'; +import { AddUserMessages, EditorFrameInstance, UserMessagesGetter } from '../types'; import { Document } from '../persistence/saved_object_store'; import { @@ -28,9 +28,8 @@ import { LensAppState, selectSavedObjectFormat, updateIndexPatterns, - updateDatasourceState, selectActiveDatasourceId, - selectFrameDatasourceAPI, + selectFramePublicAPI, } from '../state_management'; import { SaveModalContainer, runSaveLensVisualization } from './save_modal_container'; import { LensInspector } from '../lens_inspector_service'; @@ -41,10 +40,7 @@ import { createIndexPatternService, } from '../data_views_service/service'; import { replaceIndexpattern } from '../state_management/lens_slice'; -import { - filterAndSortUserMessages, - getApplicationUserMessages, -} from './get_application_user_messages'; +import { useApplicationUserMessages } from './get_application_user_messages'; export type SaveProps = Omit & { returnToOrigin: boolean; @@ -509,99 +505,25 @@ export function App({ const activeDatasourceId = useLensSelector(selectActiveDatasourceId); - const frameDatasourceAPI = useLensSelector((state) => - selectFrameDatasourceAPI(state, datasourceMap) - ); - - const [userMessages, setUserMessages] = useState([]); + const framePublicAPI = useLensSelector((state) => selectFramePublicAPI(state, datasourceMap)); - useEffect(() => { - setUserMessages([ - ...(activeDatasourceId - ? datasourceMap[activeDatasourceId].getUserMessages( - datasourceStates[activeDatasourceId].state, - { - frame: frameDatasourceAPI, - setState: (newStateOrUpdater) => { - dispatch( - updateDatasourceState({ - newDatasourceState: - typeof newStateOrUpdater === 'function' - ? newStateOrUpdater(datasourceStates[activeDatasourceId].state) - : newStateOrUpdater, - datasourceId: activeDatasourceId, - }) - ); - }, - } - ) - : []), - ...(visualization.activeId && visualization.state - ? visualizationMap[visualization.activeId]?.getUserMessages?.(visualization.state, { - frame: frameDatasourceAPI, - }) ?? [] - : []), - ...getApplicationUserMessages({ - visualizationType: persistedDoc?.visualizationType, - visualizationMap, - visualization, - activeDatasource: activeDatasourceId ? datasourceMap[activeDatasourceId] : null, - activeDatasourceState: activeDatasourceId ? datasourceStates[activeDatasourceId] : null, - core: coreStart, - dataViews: frameDatasourceAPI.dataViews, - }), - ]); - }, [ - activeDatasourceId, + const { getUserMessages, addUserMessages } = useApplicationUserMessages({ coreStart, - datasourceMap, - datasourceStates, + framePublicAPI, + activeDatasourceId, + datasourceState: + activeDatasourceId && datasourceStates[activeDatasourceId] + ? datasourceStates[activeDatasourceId] + : null, + datasource: + activeDatasourceId && datasourceMap[activeDatasourceId] + ? datasourceMap[activeDatasourceId] + : null, dispatch, - frameDatasourceAPI, - persistedDoc?.visualizationType, - visualization, - visualizationMap, - ]); - - // these are messages managed from other parts of Lens - const [additionalUserMessages, setAdditionalUserMessages] = useState>( - {} - ); - - const getUserMessages: UserMessagesGetter = (locationId, filterArgs) => - filterAndSortUserMessages( - [...userMessages, ...Object.values(additionalUserMessages)], - locationId, - filterArgs ?? {} - ); - - const addUserMessages: AddUserMessages = (messages) => { - const newMessageMap = { - ...additionalUserMessages, - }; - - const addedMessageIds: string[] = []; - messages.forEach((message) => { - if (!newMessageMap[message.uniqueId]) { - addedMessageIds.push(message.uniqueId); - newMessageMap[message.uniqueId] = message; - } - }); - - if (addedMessageIds.length) { - setAdditionalUserMessages(newMessageMap); - } - - return () => { - const withMessagesRemoved = { - ...additionalUserMessages, - }; - - addedMessageIds.forEach((id) => delete withMessagesRemoved[id]); - - setAdditionalUserMessages(withMessagesRemoved); - }; - }; + visualization: visualization.activeId ? visualizationMap[visualization.activeId] : undefined, + visualizationType: visualization.activeId, + visualizationState: visualization, + }); return ( <> diff --git a/x-pack/plugins/lens/public/app_plugin/get_application_user_messages.test.tsx b/x-pack/plugins/lens/public/app_plugin/get_application_user_messages.test.tsx index fa0ed4e21a6682..f511e7e59112ac 100644 --- a/x-pack/plugins/lens/public/app_plugin/get_application_user_messages.test.tsx +++ b/x-pack/plugins/lens/public/app_plugin/get_application_user_messages.test.tsx @@ -25,8 +25,8 @@ describe('application-level user messages', () => { getApplicationUserMessages({ visualizationType: undefined, - visualizationMap: {}, - visualization: { activeId: '', state: {} }, + visualization: undefined, + visualizationState: { activeId: '', state: {} }, activeDatasource: {} as Datasource, activeDatasourceState: null, dataViews: {} as DataViewsState, @@ -53,8 +53,8 @@ describe('application-level user messages', () => { expect( getApplicationUserMessages({ visualizationType: '123', - visualizationMap: {}, - visualization: { activeId: 'id_for_type_that_doesnt_exist', state: {} }, + visualization: undefined, + visualizationState: { activeId: 'id_for_type_that_doesnt_exist', state: {} }, activeDatasource: {} as Datasource, activeDatasourceState: null, @@ -84,8 +84,8 @@ describe('application-level user messages', () => { activeDatasource: null, visualizationType: '123', - visualizationMap: { 'some-id': {} as Visualization }, - visualization: { activeId: 'some-id', state: {} }, + visualization: {} as Visualization, + visualizationState: { activeId: 'some-id', state: {} }, activeDatasourceState: null, dataViews: {} as DataViewsState, core: {} as CoreStart, @@ -138,8 +138,8 @@ describe('application-level user messages', () => { const irrelevantProps = { dataViews: {} as DataViewsState, - visualizationMap: { foo: {} as Visualization }, - visualization: { activeId: 'foo', state: {} }, + visualization: {} as Visualization, + visualizationState: { activeId: 'foo', state: {} }, }; it('generates error if missing an index pattern', () => { diff --git a/x-pack/plugins/lens/public/app_plugin/get_application_user_messages.tsx b/x-pack/plugins/lens/public/app_plugin/get_application_user_messages.tsx index b5a2e0fe24def3..4041fce3bbd0a4 100644 --- a/x-pack/plugins/lens/public/app_plugin/get_application_user_messages.tsx +++ b/x-pack/plugins/lens/public/app_plugin/get_application_user_messages.tsx @@ -5,18 +5,27 @@ * 2.0. */ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app'; import { FormattedMessage } from '@kbn/i18n-react'; import type { CoreStart } from '@kbn/core/public'; -import type { DataViewsState, VisualizationState } from '../state_management'; +import { Dispatch } from '@reduxjs/toolkit'; +import { + updateDatasourceState, + type DataViewsState, + type VisualizationState, + DatasourceState, +} from '../state_management'; import type { + AddUserMessages, Datasource, + FramePublicAPI, UserMessage, UserMessageFilters, UserMessagesDisplayLocationId, - VisualizationMap, + UserMessagesGetter, + Visualization, } from '../types'; import { getMissingIndexPattern } from '../editor_frame_service/editor_frame/state_helpers'; @@ -26,15 +35,15 @@ import { getMissingIndexPattern } from '../editor_frame_service/editor_frame/sta export const getApplicationUserMessages = ({ visualizationType, visualization, - visualizationMap, + visualizationState, activeDatasource, activeDatasourceState, dataViews, core, }: { visualizationType: string | null | undefined; - visualization: VisualizationState | undefined; - visualizationMap: VisualizationMap; + visualization: Visualization | undefined; + visualizationState: VisualizationState | undefined; activeDatasource: Datasource | null | undefined; activeDatasourceState: { isLoading: boolean; state: unknown } | null; dataViews: DataViewsState; @@ -46,8 +55,8 @@ export const getApplicationUserMessages = ({ messages.push(getMissingVisTypeError()); } - if (visualization?.activeId && !visualizationMap[visualization.activeId]) { - messages.push(getUnknownVisualizationTypeError(visualization.activeId)); + if (visualizationState?.activeId && !visualization) { + messages.push(getUnknownVisualizationTypeError(visualizationState.activeId)); } if (!activeDatasource) { @@ -182,8 +191,8 @@ function getMissingIndexPatternsErrors( export const filterAndSortUserMessages = ( userMessages: UserMessage[], - locationId: UserMessagesDisplayLocationId | UserMessagesDisplayLocationId[] | undefined, - { dimensionId, severity }: UserMessageFilters + locationId?: UserMessagesDisplayLocationId | UserMessagesDisplayLocationId[], + { dimensionId, severity }: UserMessageFilters = {} ) => { const locationIds = Array.isArray(locationId) ? locationId @@ -231,3 +240,112 @@ function bySeverity(a: UserMessage, b: UserMessage) { } return 1; } + +export const useApplicationUserMessages = ({ + coreStart, + dispatch, + activeDatasourceId, + datasource, + datasourceState, + framePublicAPI, + visualizationType, + visualization, + visualizationState, +}: { + activeDatasourceId: string | null; + coreStart: CoreStart; + datasource: Datasource | null; + datasourceState: DatasourceState | null; + dispatch: Dispatch; + framePublicAPI: FramePublicAPI; + visualizationType: string | null; + visualizationState?: VisualizationState; + visualization?: Visualization; +}) => { + const [userMessages, setUserMessages] = useState([]); + // these are messages managed from other parts of Lens + const [additionalUserMessages, setAdditionalUserMessages] = useState>( + {} + ); + + useEffect(() => { + setUserMessages([ + ...(datasourceState && datasourceState.state && datasource && activeDatasourceId + ? datasource.getUserMessages(datasourceState.state, { + frame: framePublicAPI, + setState: (newStateOrUpdater) => { + dispatch( + updateDatasourceState({ + newDatasourceState: + typeof newStateOrUpdater === 'function' + ? newStateOrUpdater(datasourceState.state) + : newStateOrUpdater, + datasourceId: activeDatasourceId, + }) + ); + }, + }) + : []), + ...(visualizationState?.activeId && visualizationState.state + ? visualization?.getUserMessages?.(visualizationState.state, { + frame: framePublicAPI, + }) ?? [] + : []), + ...getApplicationUserMessages({ + visualizationType, + visualization, + visualizationState, + activeDatasource: datasource, + activeDatasourceState: datasourceState, + core: coreStart, + dataViews: framePublicAPI.dataViews, + }), + ]); + }, [ + activeDatasourceId, + datasource, + datasourceState, + dispatch, + framePublicAPI, + visualization, + visualizationState, + visualizationType, + coreStart, + ]); + + const getUserMessages: UserMessagesGetter = (locationId, filterArgs) => + filterAndSortUserMessages( + [...userMessages, ...Object.values(additionalUserMessages)], + locationId, + filterArgs ?? {} + ); + + const addUserMessages: AddUserMessages = (messages) => { + const newMessageMap = { + ...additionalUserMessages, + }; + + const addedMessageIds: string[] = []; + messages.forEach((message) => { + if (!newMessageMap[message.uniqueId]) { + addedMessageIds.push(message.uniqueId); + newMessageMap[message.uniqueId] = message; + } + }); + + if (addedMessageIds.length) { + setAdditionalUserMessages(newMessageMap); + } + + return () => { + const withMessagesRemoved = { + ...additionalUserMessages, + }; + + addedMessageIds.forEach((id) => delete withMessagesRemoved[id]); + + setAdditionalUserMessages(withMessagesRemoved); + }; + }; + return { getUserMessages, addUserMessages }; +}; diff --git a/x-pack/plugins/lens/public/datasources/form_based/form_based.test.ts b/x-pack/plugins/lens/public/datasources/form_based/form_based.test.ts index 591cfc322a8a07..acc77bd34c39cd 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/form_based.test.ts +++ b/x-pack/plugins/lens/public/datasources/form_based/form_based.test.ts @@ -26,7 +26,6 @@ import { Datasource, FramePublicAPI, OperationDescriptor, - FrameDatasourceAPI, UserMessage, } from '../../types'; import { getFieldByNameFactory } from './pure_helpers'; @@ -49,8 +48,9 @@ import { import { createMockedFullReference } from './operations/mocks'; import { cloneDeep } from 'lodash'; import { Datatable, DatatableColumn } from '@kbn/expressions-plugin/common'; -import { createMockFramePublicAPI } from '../../mocks'; import { filterAndSortUserMessages } from '../../app_plugin/get_application_user_messages'; +import { createMockFramePublicAPI } from '../../mocks'; +import { createMockDataViewsState } from '../../data_views_service/mocks'; jest.mock('./loader'); jest.mock('../../id_generator'); @@ -3062,22 +3062,6 @@ describe('IndexPattern Data Source', () => { }); describe('#getUserMessages', () => { - function createMockFrameDatasourceAPI({ - activeData, - dataViews, - }: Partial> & { - dataViews?: Partial; - }): FrameDatasourceAPI { - return { - ...createMockFramePublicAPI({ - activeData, - dataViews, - }), - query: { query: '', language: 'kuery' }, - filters: [], - }; - } - describe('error messages', () => { it('should generate error messages for a single layer', () => { (getErrorMessages as jest.Mock).mockClear(); @@ -3094,7 +3078,9 @@ describe('IndexPattern Data Source', () => { }; expect( FormBasedDatasource.getUserMessages(state, { - frame: createMockFrameDatasourceAPI({ dataViews: { indexPatterns } }), + frame: createMockFramePublicAPI({ + dataViews: createMockDataViewsState({ indexPatterns }), + }), setState: () => {}, }) ).toMatchInlineSnapshot(` @@ -3146,7 +3132,9 @@ describe('IndexPattern Data Source', () => { }; expect( FormBasedDatasource.getUserMessages(state, { - frame: createMockFrameDatasourceAPI({ dataViews: { indexPatterns } }), + frame: createMockFramePublicAPI({ + dataViews: createMockDataViewsState({ indexPatterns }), + }), setState: () => {}, }) ).toMatchInlineSnapshot(` @@ -3235,7 +3223,9 @@ describe('IndexPattern Data Source', () => { (getErrorMessages as jest.Mock).mockReturnValueOnce([]); const messages = FormBasedDatasource.getUserMessages(state, { - frame: createMockFrameDatasourceAPI({ dataViews: { indexPatterns } }), + frame: createMockFramePublicAPI({ + dataViews: createMockDataViewsState({ indexPatterns }), + }), setState: () => {}, }); @@ -3273,7 +3263,9 @@ describe('IndexPattern Data Source', () => { ] as ReturnType); const messages = FormBasedDatasource.getUserMessages(state, { - frame: createMockFrameDatasourceAPI({ dataViews: { indexPatterns } }), + frame: createMockFramePublicAPI({ + dataViews: createMockDataViewsState({ indexPatterns }), + }), setState: () => {}, }); @@ -3303,7 +3295,7 @@ describe('IndexPattern Data Source', () => { describe('warning messages', () => { let state: FormBasedPrivateState; - let framePublicAPI: FrameDatasourceAPI; + let framePublicAPI: FramePublicAPI; beforeEach(() => { (getErrorMessages as jest.Mock).mockReturnValueOnce([]); @@ -3385,7 +3377,7 @@ describe('IndexPattern Data Source', () => { currentIndexPatternId: '1', }; - framePublicAPI = createMockFrameDatasourceAPI({ + framePublicAPI = createMockFramePublicAPI({ activeData: { first: { type: 'datatable', @@ -3419,9 +3411,9 @@ describe('IndexPattern Data Source', () => { ], }, }, - dataViews: { + dataViews: createMockDataViewsState({ indexPatterns: expectedIndexPatterns, - }, + }), }); }); @@ -3549,13 +3541,13 @@ describe('IndexPattern Data Source', () => { currentIndexPatternId: '1', }, { - frame: createMockFrameDatasourceAPI({ + frame: createMockFramePublicAPI({ activeData: { first: createDatatableForLayer(0), }, - dataViews: { + dataViews: createMockDataViewsState({ indexPatterns: expectedIndexPatterns, - }, + }), }), setState: () => {}, visualizationInfo: { layers: [] }, @@ -3574,14 +3566,14 @@ describe('IndexPattern Data Source', () => { currentIndexPatternId: '1', }; const messages = FormBasedDatasource.getUserMessages!(state, { - frame: createMockFrameDatasourceAPI({ + frame: createMockFramePublicAPI({ activeData: { first: createDatatableForLayer(0), second: createDatatableForLayer(1), }, - dataViews: { + dataViews: createMockDataViewsState({ indexPatterns: expectedIndexPatterns, - }, + }), }), setState: () => {}, visualizationInfo: { layers: [] }, diff --git a/x-pack/plugins/lens/public/datasources/form_based/form_based.tsx b/x-pack/plugins/lens/public/datasources/form_based/form_based.tsx index a458c6f804da41..8099a787437baa 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/form_based.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/form_based.tsx @@ -38,7 +38,6 @@ import type { IndexPatternRef, DataSourceInfo, UserMessage, - FrameDatasourceAPI, StateSetter, IndexPatternMap, } from '../../types'; @@ -749,18 +748,12 @@ export function getFormBasedDatasource({ getDatasourceSuggestionsForVisualizeField, getDatasourceSuggestionsForVisualizeCharts, - getUserMessages(state, { frame: frameDatasourceAPI, setState, visualizationInfo }) { + getUserMessages(state, { frame: framePublicAPI, setState, visualizationInfo }) { if (!state) { return []; } - const layerErrorMessages = getLayerErrorMessages( - state, - frameDatasourceAPI, - setState, - core, - data - ); + const layerErrorMessages = getLayerErrorMessages(state, framePublicAPI, setState, core, data); const dimensionErrorMessages = getInvalidDimensionErrorMessages( state, @@ -770,8 +763,8 @@ export function getFormBasedDatasource({ return !isColumnInvalid( layer, columnId, - frameDatasourceAPI.dataViews.indexPatterns[layer.indexPatternId], - frameDatasourceAPI.dateRange, + framePublicAPI.dataViews.indexPatterns[layer.indexPatternId], + framePublicAPI.dateRange, uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET) ); } @@ -779,11 +772,8 @@ export function getFormBasedDatasource({ const warningMessages = [ ...[ - ...(getStateTimeShiftWarningMessages( - data.datatableUtilities, - state, - frameDatasourceAPI - ) || []), + ...(getStateTimeShiftWarningMessages(data.datatableUtilities, state, framePublicAPI) || + []), ].map((longMessage) => { const message: UserMessage = { severity: 'warning', @@ -798,14 +788,14 @@ export function getFormBasedDatasource({ ...getPrecisionErrorWarningMessages( data.datatableUtilities, state, - frameDatasourceAPI, + framePublicAPI, core.docLinks, setState ), - ...getUnsupportedOperationsWarningMessage(state, frameDatasourceAPI, core.docLinks), + ...getUnsupportedOperationsWarningMessage(state, framePublicAPI, core.docLinks), ]; - const infoMessages = getNotifiableFeatures(state, frameDatasourceAPI, visualizationInfo); + const infoMessages = getNotifiableFeatures(state, framePublicAPI, visualizationInfo); return layerErrorMessages.concat(dimensionErrorMessages, warningMessages, infoMessages); }, @@ -922,12 +912,12 @@ function blankLayer(indexPatternId: string, linkToLayers?: string[]): FormBasedL function getLayerErrorMessages( state: FormBasedPrivateState, - frameDatasourceAPI: FrameDatasourceAPI, + framePublicAPI: FramePublicAPI, setState: StateSetter, core: CoreStart, data: DataPublicPluginStart ) { - const indexPatterns = frameDatasourceAPI.dataViews.indexPatterns; + const indexPatterns = framePublicAPI.dataViews.indexPatterns; const layerErrors: UserMessage[][] = Object.entries(state.layers) .filter(([_, layer]) => !!indexPatterns[layer.indexPatternId]) @@ -954,7 +944,7 @@ function getLayerErrorMessages( { - const newState = await error.fixAction?.newState(frameDatasourceAPI); + const newState = await error.fixAction?.newState(framePublicAPI); if (newState) { setState(newState); } diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/index.ts b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/index.ts index a5ac36a5b4ad1a..3c62f34cbc1f21 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/index.ts +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/index.ts @@ -51,7 +51,7 @@ import { import { staticValueOperation } from './static_value'; import { lastValueOperation } from './last_value'; import type { - FrameDatasourceAPI, + FramePublicAPI, IndexPattern, IndexPatternField, OperationMetadata, @@ -477,7 +477,7 @@ export type FieldBasedOperationErrorMessage = newState: ( data: DataPublicPluginStart, core: CoreStart, - frame: FrameDatasourceAPI, + frame: FramePublicAPI, layerId: string ) => Promise; }; diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/terms/helpers.test.ts b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/terms/helpers.test.ts index 6be9e8a76fa3e3..583f3ff5015c2d 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/terms/helpers.test.ts +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/terms/helpers.test.ts @@ -7,7 +7,7 @@ import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; import { coreMock as corePluginMock } from '@kbn/core/public/mocks'; -import type { FrameDatasourceAPI } from '../../../../../types'; +import type { FramePublicAPI } from '../../../../../types'; import type { CountIndexPatternColumn } from '..'; import type { TermsIndexPatternColumn } from './types'; import type { GenericIndexPatternColumn } from '../../../form_based'; @@ -245,7 +245,7 @@ describe('getDisallowedTermsMessage()', () => { fromDate: '2020', toDate: '2021', }, - } as unknown as FrameDatasourceAPI, + } as unknown as FramePublicAPI, 'first' ); @@ -299,7 +299,7 @@ describe('getDisallowedTermsMessage()', () => { rows: [{ col1: 'myTerm' }, { col1: 'myOtherTerm' }], }, }, - } as unknown as FrameDatasourceAPI, + } as unknown as FramePublicAPI, 'first' ); @@ -335,7 +335,7 @@ describe('getDisallowedTermsMessage()', () => { fromDate: '2020', toDate: '2021', }, - } as unknown as FrameDatasourceAPI, + } as unknown as FramePublicAPI, 'first' ); @@ -385,7 +385,7 @@ describe('getDisallowedTermsMessage()', () => { ], }, }, - } as unknown as FrameDatasourceAPI, + } as unknown as FramePublicAPI, 'first' ); diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/terms/helpers.ts b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/terms/helpers.ts index a1b528f2d0f7ff..cb3ed583d7cdaf 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/terms/helpers.ts +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/terms/helpers.ts @@ -17,7 +17,7 @@ import { GenericIndexPatternColumn, operationDefinitionMap } from '..'; import { defaultLabel } from '../filters'; import { isReferenced } from '../../layer_helpers'; -import type { FrameDatasourceAPI, IndexPattern, IndexPatternField } from '../../../../../types'; +import type { FramePublicAPI, IndexPattern, IndexPatternField } from '../../../../../types'; import type { FiltersIndexPatternColumn } from '..'; import type { TermsIndexPatternColumn } from './types'; import type { LastValueIndexPatternColumn } from '../last_value'; @@ -126,7 +126,7 @@ export function getDisallowedTermsMessage( newState: async ( data: DataPublicPluginStart, core: CoreStart, - frame: FrameDatasourceAPI, + frame: FramePublicAPI, layerId: string ) => { const currentColumn = layer.columns[columnId] as TermsIndexPatternColumn; diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/terms/terms.test.tsx b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/terms/terms.test.tsx index c5af3b5b40e2f6..a74872a46f907c 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/terms/terms.test.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/terms/terms.test.tsx @@ -33,7 +33,7 @@ import { operationDefinitionMap, } from '..'; import { FormBasedLayer, FormBasedPrivateState } from '../../../types'; -import { FrameDatasourceAPI } from '../../../../../types'; +import { FramePublicAPI } from '../../../../../types'; import { DateHistogramIndexPatternColumn } from '../date_histogram'; import { getOperationSupportMatrix } from '../../../dimension_panel/operation_support'; import { FieldSelect } from '../../../dimension_panel/field_select'; @@ -2867,7 +2867,7 @@ describe('terms', () => { fromDate: '2020', toDate: '2021', }, - } as unknown as FrameDatasourceAPI, + } as unknown as FramePublicAPI, 'first' ); expect(newLayer.columns.col1).toEqual( diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/layer_helpers.ts b/x-pack/plugins/lens/public/datasources/form_based/operations/layer_helpers.ts index 37a742b426edb0..6ab10a097010d0 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/layer_helpers.ts +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/layer_helpers.ts @@ -13,7 +13,7 @@ import { DataPublicPluginStart, UI_SETTINGS } from '@kbn/data-plugin/public'; import type { DateRange } from '../../../../common/types'; import type { DatasourceFixAction, - FrameDatasourceAPI, + FramePublicAPI, IndexPattern, IndexPatternField, OperationMetadata, @@ -1594,7 +1594,7 @@ export function getErrorMessages( fixAction: errorMessage.fixAction ? { ...errorMessage.fixAction, - newState: async (frame: FrameDatasourceAPI) => ({ + newState: async (frame: FramePublicAPI) => ({ ...state, layers: { ...state.layers, diff --git a/x-pack/plugins/lens/public/datasources/text_based/text_based_languages.test.ts b/x-pack/plugins/lens/public/datasources/text_based/text_based_languages.test.ts index 0b0437cc96c0b8..8aab2f3670959c 100644 --- a/x-pack/plugins/lens/public/datasources/text_based/text_based_languages.test.ts +++ b/x-pack/plugins/lens/public/datasources/text_based/text_based_languages.test.ts @@ -13,7 +13,7 @@ import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; import { getTextBasedDatasource } from './text_based_languages'; import { generateId } from '../../id_generator'; -import { DatasourcePublicAPI, Datasource, FrameDatasourceAPI } from '../../types'; +import { DatasourcePublicAPI, Datasource, FramePublicAPI } from '../../types'; jest.mock('../../id_generator'); @@ -551,7 +551,7 @@ describe('Textbased Data Source', () => { } as unknown as TextBasedPrivateState; expect( TextBasedDatasource.getUserMessages(state, { - frame: { dataViews: indexPatterns } as unknown as FrameDatasourceAPI, + frame: { dataViews: indexPatterns } as unknown as FramePublicAPI, setState: () => {}, }) ).toMatchInlineSnapshot(` diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.test.tsx index c5ec748aa9bf09..b0729cb489ba70 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.test.tsx @@ -477,12 +477,14 @@ describe('ConfigPanel', () => { expect(visualizationMap.testVis.setDimension).toHaveBeenCalledWith({ columnId: 'newId', frame: { + dataViews: expect.anything(), activeData: undefined, datasourceLayers: { a: expect.anything(), }, dateRange: expect.anything(), - dataViews: expect.anything(), + filters: [], + query: undefined, }, groupId: 'a', layerId: 'newId', diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx index 3d90273ef0d143..0362f130be87df 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx @@ -38,6 +38,7 @@ import { DatasourceMock, createExpressionRendererMock, mockStoreDeps, + renderWithReduxStore, } from '../../mocks'; import { inspectorPluginMock } from '@kbn/inspector-plugin/public/mocks'; import { ReactExpressionRendererType } from '@kbn/expressions-plugin/public'; @@ -283,7 +284,7 @@ describe('editor_frame', () => { const props = { ...getDefaultProps(), visualizationMap: { - testVis: mockVisualization, + testVis: { ...mockVisualization, toExpression: () => null }, }, datasourceMap: { testDatasource: mockDatasource, @@ -291,18 +292,23 @@ describe('editor_frame', () => { ExpressionRenderer: expressionRendererMock, }; - await mountWithProvider(, { - preloadedState: { - activeDatasourceId: 'testDatasource', - visualization: { activeId: mockVisualization.id, state: {} }, - datasourceStates: { - testDatasource: { - isLoading: false, - state: '', + renderWithReduxStore( + , + {}, + { + preloadedState: { + activeDatasourceId: 'testDatasource', + visualization: { activeId: mockVisualization.id, state: {} }, + datasourceStates: { + testDatasource: { + isLoading: false, + state: '', + }, }, }, - }, - }); + } + ); + const updatedState = {}; const setDatasourceState = (mockDatasource.DataPanelComponent as jest.Mock).mock.calls[0][0] .setState; diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts index e12182cd07bffc..466773ec1c6b2d 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts @@ -35,7 +35,7 @@ import type { import { buildExpression } from './expression_helpers'; import { Document } from '../../persistence/saved_object_store'; import { getActiveDatasourceIdFromDoc, sortDataViewRefs } from '../../utils'; -import type { DatasourceStates, VisualizationState } from '../../state_management'; +import type { DatasourceState, DatasourceStates, VisualizationState } from '../../state_management'; import { readFromStorage } from '../../settings_storage'; import { loadIndexPatternRefs, loadIndexPatterns } from '../../data_views_service/loader'; import { getDatasourceLayers } from '../../state_management/utils'; @@ -461,7 +461,7 @@ export async function persistedStateToExpression( export function getMissingIndexPattern( currentDatasource: Datasource | null | undefined, - currentDatasourceState: { isLoading: boolean; state: unknown } | null, + currentDatasourceState: DatasourceState | null, indexPatterns: IndexPatternMap ) { if ( diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.test.tsx index 09e5c6406e7937..24fe5c971558cd 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.test.tsx @@ -381,8 +381,9 @@ describe('suggestion_panel', () => { }, ] as Suggestion[]); - (mockVisualization.toPreviewExpression as jest.Mock).mockReturnValueOnce(undefined); - (mockVisualization.toPreviewExpression as jest.Mock).mockReturnValueOnce('test | expression'); + (mockVisualization.toPreviewExpression as jest.Mock) + .mockReturnValue(undefined) + .mockReturnValueOnce('test | expression'); mockDatasource.toExpression.mockReturnValue('datasource_expression'); mountWithProvider(); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx index 51f0e3310ccdbc..a4e65b280d2037 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx @@ -41,7 +41,6 @@ import { VisualizationMap, DatasourceLayers, UserMessagesGetter, - FrameDatasourceAPI, } from '../../types'; import { getSuggestions, switchToSuggestion } from './suggestion_helpers'; import { getDatasourceExpressionsByLayers } from './expression_helpers'; @@ -63,7 +62,7 @@ import { selectChangesApplied, applyChanges, selectStagedActiveData, - selectFrameDatasourceAPI, + selectFramePublicAPI, } from '../../state_management'; import { filterAndSortUserMessages } from '../../app_plugin/get_application_user_messages'; const MAX_SUGGESTIONS_DISPLAYED = 5; @@ -74,7 +73,7 @@ const configurationsValid = ( currentDatasourceState: unknown, currentVisualization: Visualization, currentVisualizationState: unknown, - frame: FrameDatasourceAPI + frame: FramePublicAPI ): boolean => { try { return ( @@ -241,9 +240,7 @@ export function SuggestionPanel({ const currentVisualization = useLensSelector(selectCurrentVisualization); const currentDatasourceStates = useLensSelector(selectCurrentDatasourceStates); - const frameDatasourceAPI = useLensSelector((state) => - selectFrameDatasourceAPI(state, datasourceMap) - ); + const framePublicAPI = useLensSelector((state) => selectFramePublicAPI(state, datasourceMap)); const changesApplied = useLensSelector(selectChangesApplied); // get user's selection from localStorage, this key defines if the suggestions panel will be hidden or not const [hideSuggestions, setHideSuggestions] = useLocalStorage( @@ -289,7 +286,7 @@ export function SuggestionPanel({ suggestionDatasourceState, visualizationMap[visualizationId], suggestionVisualizationState, - frameDatasourceAPI + framePublicAPI ) ); } diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.test.tsx index 6323dbf5c96f40..a8e834e43881c2 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.test.tsx @@ -380,7 +380,7 @@ describe('workspace_panel', () => { }} framePublicAPI={framePublicAPI} visualizationMap={{ - testVis: mockVisualization, + testVis: { ...mockVisualization, toExpression: () => null }, }} ExpressionRenderer={expressionRendererMock} />, diff --git a/x-pack/plugins/lens/public/embeddable/embeddable.tsx b/x-pack/plugins/lens/public/embeddable/embeddable.tsx index a0a2faec5294d6..b9d0bae1979097 100644 --- a/x-pack/plugins/lens/public/embeddable/embeddable.tsx +++ b/x-pack/plugins/lens/public/embeddable/embeddable.tsx @@ -97,7 +97,7 @@ import { GetCompatibleCellValueActions, UserMessage, IndexPatternRef, - FrameDatasourceAPI, + FramePublicAPI, AddUserMessages, isMessageRemovable, UserMessagesGetter, @@ -608,11 +608,14 @@ export class Embeddable userMessages.push( ...getApplicationUserMessages({ visualizationType: this.savedVis?.visualizationType, - visualization: { + visualizationState: { state: this.activeVisualizationState, activeId: this.activeVisualizationId, }, - visualizationMap: this.deps.visualizationMap, + visualization: + this.activeVisualizationId && this.deps.visualizationMap[this.activeVisualizationId] + ? this.deps.visualizationMap[this.activeVisualizationId] + : undefined, activeDatasource: this.activeDatasource, activeDatasourceState: { isLoading: !this.activeDatasourceState, @@ -631,7 +634,7 @@ export class Embeddable } const mergedSearchContext = this.getMergedSearchContext(); - const frameDatasourceAPI: FrameDatasourceAPI = { + const framePublicAPI: FramePublicAPI = { dataViews: { indexPatterns: this.indexPatterns, indexPatternRefs: this.indexPatternRefs, @@ -658,14 +661,14 @@ export class Embeddable userMessages.push( ...(this.activeDatasource?.getUserMessages(this.activeDatasourceState, { setState: () => {}, - frame: frameDatasourceAPI, + frame: framePublicAPI, visualizationInfo: this.activeVisualization?.getVisualizationInfo?.( this.activeVisualizationState, - frameDatasourceAPI + framePublicAPI ), }) ?? []), ...(this.activeVisualization?.getUserMessages?.(this.activeVisualizationState, { - frame: frameDatasourceAPI, + frame: framePublicAPI, }) ?? []) ); diff --git a/x-pack/plugins/lens/public/mocks/datasource_mock.tsx b/x-pack/plugins/lens/public/mocks/datasource_mock.tsx index e4f9f5c88fdc1d..c92c3007a800aa 100644 --- a/x-pack/plugins/lens/public/mocks/datasource_mock.tsx +++ b/x-pack/plugins/lens/public/mocks/datasource_mock.tsx @@ -44,7 +44,9 @@ export function createMockDatasource( getRenderEventCounters: jest.fn((_state) => []), getPublicAPI: jest.fn().mockReturnValue(publicAPIMock), initialize: jest.fn((_state?) => {}), - toExpression: jest.fn((_frame, _state, _indexPatterns, dateRange, nowInstant) => null), + toExpression: jest.fn( + (_frame, _state, _indexPatterns, dateRange, nowInstant) => 'datasource_expression' + ), insertLayer: jest.fn((_state, _newLayerId) => ({})), removeLayer: jest.fn((state, layerId) => ({ newState: state, removedLayerIds: [layerId] })), cloneLayer: jest.fn((_state, _layerId, _newLayerId, getNewId) => {}), @@ -62,7 +64,7 @@ export function createMockDatasource( getUserMessages: jest.fn((_state, _deps) => []), checkIntegrity: jest.fn((_state, _indexPatterns) => []), isTimeBased: jest.fn(), - isEqual: jest.fn(), + isEqual: jest.fn((a, b, c, d) => a === c), getUsedDataView: jest.fn((state, layer) => 'mockip'), getUsedDataViews: jest.fn(), onRefreshIndexPattern: jest.fn(), diff --git a/x-pack/plugins/lens/public/mocks/index.ts b/x-pack/plugins/lens/public/mocks/index.ts index 74fbe267d635a4..6cc3ff02ff92e4 100644 --- a/x-pack/plugins/lens/public/mocks/index.ts +++ b/x-pack/plugins/lens/public/mocks/index.ts @@ -8,7 +8,7 @@ import { DragContextState, DragContextValue } from '@kbn/dom-drag-drop'; import { DatatableColumnType } from '@kbn/expressions-plugin/common'; import { createMockDataViewsState } from '../data_views_service/mocks'; -import { FramePublicAPI, FrameDatasourceAPI } from '../types'; +import { FramePublicAPI } from '../types'; export { mockDataPlugin } from './data_plugin_mock'; export { visualizationMap, @@ -32,40 +32,16 @@ export { lensPluginMock } from './lens_plugin_mock'; export type FrameMock = jest.Mocked; -export const createMockFramePublicAPI = ({ - datasourceLayers, - dateRange, - dataViews, - activeData, -}: Partial> & { - dataViews?: Partial; -} = {}): FrameMock => ({ - datasourceLayers: datasourceLayers ?? {}, - dateRange: dateRange ?? { +export const createMockFramePublicAPI = (overrides: Partial = {}): FrameMock => ({ + datasourceLayers: {}, + dateRange: { fromDate: '2022-03-17T08:25:00.000Z', toDate: '2022-04-17T08:25:00.000Z', }, - dataViews: createMockDataViewsState(dataViews), - activeData, -}); - -export type FrameDatasourceMock = jest.Mocked; - -export const createMockFrameDatasourceAPI = ({ - datasourceLayers, - dateRange, - dataViews, - query, - filters, -}: Partial = {}): FrameDatasourceMock => ({ - datasourceLayers: datasourceLayers ?? {}, - dateRange: dateRange ?? { - fromDate: '2022-03-17T08:25:00.000Z', - toDate: '2022-04-17T08:25:00.000Z', - }, - query: query ?? { query: '', language: 'lucene' }, - filters: filters ?? [], - dataViews: createMockDataViewsState(dataViews), + dataViews: createMockDataViewsState(), + query: { query: '', language: 'lucene' }, + filters: [], + ...overrides, }); export function createMockedDragDropContext( diff --git a/x-pack/plugins/lens/public/mocks/store_mocks.tsx b/x-pack/plugins/lens/public/mocks/store_mocks.tsx index cafb7796969c8e..9a3d33afa1aa79 100644 --- a/x-pack/plugins/lens/public/mocks/store_mocks.tsx +++ b/x-pack/plugins/lens/public/mocks/store_mocks.tsx @@ -13,26 +13,14 @@ import { act } from 'react-dom/test-utils'; import { PreloadedState } from '@reduxjs/toolkit'; import { RenderOptions, render } from '@testing-library/react'; import { I18nProvider } from '@kbn/i18n-react'; -// imported to prevent a type error from testing library https://github.com/testing-library/react-testing-library/issues/587 -import * as ___ from '@testing-library/dom'; - import { LensAppServices } from '../app_plugin/types'; -import { - makeConfigureStore, - LensAppState, - LensState, - LensStoreDeps, - LensRootStore, -} from '../state_management'; +import { makeConfigureStore, LensAppState, LensState, LensStoreDeps } from '../state_management'; import { getResolvedDateRange } from '../utils'; import { DatasourceMap, VisualizationMap } from '../types'; import { mockVisualizationMap } from './visualization_mock'; import { mockDatasourceMap } from './datasource_mock'; import { makeDefaultServices } from './services_mock'; -// preventing a type error from testing library https://github.com/testing-library/react-testing-library/issues/587 -export const unusedFn = () => ___; - export const mockStoreDeps = (deps?: { lensServices?: LensAppServices; datasourceMap?: DatasourceMap; @@ -85,7 +73,8 @@ export const renderWithReduxStore = ( preloadedState: {}, storeDeps: mockStoreDeps(), } -): ReturnType & { store: LensRootStore } => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any +): any => { const { store } = makeLensStore({ preloadedState, storeDeps }); const Wrapper: React.FC<{ diff --git a/x-pack/plugins/lens/public/mocks/visualization_mock.tsx b/x-pack/plugins/lens/public/mocks/visualization_mock.tsx index 80f20e8c3af4eb..3d25f7e29864b7 100644 --- a/x-pack/plugins/lens/public/mocks/visualization_mock.tsx +++ b/x-pack/plugins/lens/public/mocks/visualization_mock.tsx @@ -44,9 +44,9 @@ export function createMockVisualization(id = 'testVis'): jest.Mocked null), - toPreviewExpression: jest.fn((_state, _frame) => null), - + toExpression: jest.fn((_state, _frame) => 'expression'), + toPreviewExpression: jest.fn((_state, _frame) => 'expression'), + getUserMessages: jest.fn((_state) => []), setDimension: jest.fn(), removeDimension: jest.fn(), DimensionEditorComponent: jest.fn(() =>
    ), diff --git a/x-pack/plugins/lens/public/state_management/selectors.ts b/x-pack/plugins/lens/public/state_management/selectors.ts index 6bf40638554f94..7572c312872973 100644 --- a/x-pack/plugins/lens/public/state_management/selectors.ts +++ b/x-pack/plugins/lens/public/state_management/selectors.ts @@ -220,10 +220,10 @@ export const selectFramePublicAPI = createSelector( selectCurrentDatasourceStates, selectActiveData, selectInjectedDependencies as SelectInjectedDependenciesFunction, - selectResolvedDateRange, selectDataViews, + selectExecutionContext, ], - (datasourceStates, activeData, datasourceMap, dateRange, dataViews) => { + (datasourceStates, activeData, datasourceMap, dataViews, context) => { return { datasourceLayers: getDatasourceLayers( datasourceStates, @@ -231,13 +231,8 @@ export const selectFramePublicAPI = createSelector( dataViews.indexPatterns ), activeData, - dateRange, dataViews, + ...context, }; } ); - -export const selectFrameDatasourceAPI = createSelector( - [selectFramePublicAPI, selectExecutionContext], - (framePublicAPI, context) => ({ ...context, ...framePublicAPI }) -); diff --git a/x-pack/plugins/lens/public/state_management/types.ts b/x-pack/plugins/lens/public/state_management/types.ts index c2d0e1ef2e386b..85e7dae2f92ce9 100644 --- a/x-pack/plugins/lens/public/state_management/types.ts +++ b/x-pack/plugins/lens/public/state_management/types.ts @@ -34,7 +34,12 @@ export interface DataViewsState { indexPatterns: Record; } -export type DatasourceStates = Record; +export interface DatasourceState { + isLoading: boolean; + state: unknown; +} + +export type DatasourceStates = Record; export interface PreviewState { visualization: VisualizationState; datasourceStates: DatasourceStates; diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index 70ee578c47cc25..6ac2b98569d7f5 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -460,7 +460,7 @@ export interface Datasource { getUserMessages: ( state: T, deps: { - frame: FrameDatasourceAPI; + frame: FramePublicAPI; setState: StateSetter; visualizationInfo?: VisualizationInfo; } @@ -513,7 +513,7 @@ export interface Datasource { export interface DatasourceFixAction { label: string; - newState: (frame: FrameDatasourceAPI) => Promise; + newState: (frame: FramePublicAPI) => Promise; } /** @@ -925,6 +925,8 @@ export interface VisualizationSuggestion { export type DatasourceLayers = Partial>; export interface FramePublicAPI { + query: Query; + filters: Filter[]; datasourceLayers: DatasourceLayers; dateRange: DateRange; /** @@ -936,11 +938,6 @@ export interface FramePublicAPI { dataViews: DataViewsState; } -export interface FrameDatasourceAPI extends FramePublicAPI { - query: Query; - filters: Filter[]; -} - /** * A visualization type advertised to the user in the chart switcher */ diff --git a/x-pack/plugins/lens/public/visualizations/xy/__snapshots__/to_expression.test.ts.snap b/x-pack/plugins/lens/public/visualizations/xy/__snapshots__/to_expression.test.ts.snap index 0cb3d014853fe4..7dfe39667fd222 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/__snapshots__/to_expression.test.ts.snap +++ b/x-pack/plugins/lens/public/visualizations/xy/__snapshots__/to_expression.test.ts.snap @@ -27,6 +27,11 @@ Object { "layers": Array [ Object { "chain": Array [ + Object { + "arguments": Object {}, + "function": "datasource_expression", + "type": "function", + }, Object { "arguments": Object { "accessors": Array [ diff --git a/x-pack/plugins/lens/public/visualizations/xy/to_expression.test.ts b/x-pack/plugins/lens/public/visualizations/xy/to_expression.test.ts index 9cdf33c134c8c4..2e63f26413c5e4 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/to_expression.test.ts +++ b/x-pack/plugins/lens/public/visualizations/xy/to_expression.test.ts @@ -265,7 +265,7 @@ describe('#toExpression', () => { expect(mockDatasource.publicAPIMock.getOperationForColumnId).toHaveBeenCalledWith('c'); expect(mockDatasource.publicAPIMock.getOperationForColumnId).toHaveBeenCalledWith('d'); expect( - (expression.chain[0].arguments.layers[0] as Ast).chain[0].arguments.columnToLabel + (expression.chain[0].arguments.layers[0] as Ast).chain[1].arguments.columnToLabel ).toEqual([ JSON.stringify({ b: 'col_b', @@ -536,13 +536,18 @@ describe('#toExpression', () => { datasourceExpressionsByLayers ) as Ast; - function getYConfigColorForLayer(ast: Ast, index: number) { + function getYConfigColorForDataLayer(ast: Ast, index: number) { + return ( + (ast.chain[0].arguments.layers[index] as Ast).chain[1].arguments.decorations[0] as Ast + ).chain[0].arguments?.color; + } + function getYConfigColorForReferenceLayer(ast: Ast, index: number) { return ( (ast.chain[0].arguments.layers[index] as Ast).chain[0].arguments.decorations[0] as Ast - ).chain[0].arguments.color; + ).chain[0].arguments?.color; } - expect(getYConfigColorForLayer(expression, 0)).toBeUndefined(); - expect(getYConfigColorForLayer(expression, 1)).toEqual([defaultReferenceLineColor]); + expect(getYConfigColorForDataLayer(expression, 0)).toBeUndefined(); + expect(getYConfigColorForReferenceLayer(expression, 1)).toEqual([defaultReferenceLineColor]); }); it('should ignore annotation layers with no event configured', () => { diff --git a/x-pack/plugins/lists/public/exceptions/components/builder/entry_renderer.test.tsx b/x-pack/plugins/lists/public/exceptions/components/builder/entry_renderer.test.tsx index b73379acd1518c..7edcf5b53f724b 100644 --- a/x-pack/plugins/lists/public/exceptions/components/builder/entry_renderer.test.tsx +++ b/x-pack/plugins/lists/public/exceptions/components/builder/entry_renderer.test.tsx @@ -25,7 +25,7 @@ import { useFindListsBySize } from '@kbn/securitysolution-list-hooks'; import type { FieldSpec } from '@kbn/data-plugin/common'; import { fields, getField } from '@kbn/data-plugin/common/mocks'; import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks'; -import { waitFor } from '@testing-library/dom'; +import { waitFor } from '@testing-library/react'; import { ReactWrapper, mount } from 'enzyme'; import { getFoundListsBySizeSchemaMock } from '../../../../common/schemas/response/found_lists_by_size_schema.mock'; diff --git a/x-pack/plugins/lists/server/routes/utils/build_siem_response.ts b/x-pack/plugins/lists/server/routes/utils/build_siem_response.ts index 4a5ed5cc4b0e0b..d61a02a869d28a 100644 --- a/x-pack/plugins/lists/server/routes/utils/build_siem_response.ts +++ b/x-pack/plugins/lists/server/routes/utils/build_siem_response.ts @@ -49,7 +49,7 @@ export class SiemResponseFactory { constructor(private response: KibanaResponseFactory) {} // eslint-disable-next-line @typescript-eslint/explicit-function-return-type - error({ statusCode, body, headers }: CustomHttpResponseOptions) { + error({ statusCode, body, headers, bypassErrorFormat }: CustomHttpResponseOptions) { // KibanaResponse is not exported so we cannot use a return type here and that is why the linter is turned off above const contentType: CustomHttpResponseOptions['headers'] = { 'content-type': 'application/json', @@ -59,10 +59,14 @@ export class SiemResponseFactory { ...(headers ?? {}), }; + const formattedBody = bypassErrorFormat + ? body + : { message: body ?? statusToErrorMessage(statusCode) }; + return this.response.custom({ body: Buffer.from( JSON.stringify({ - message: body ?? statusToErrorMessage(statusCode), + ...formattedBody, status_code: statusCode, }) ), diff --git a/x-pack/plugins/log_explorer/common/constants.ts b/x-pack/plugins/log_explorer/common/constants.ts index 4a93192b13acb3..5aeb491b5a9d23 100644 --- a/x-pack/plugins/log_explorer/common/constants.ts +++ b/x-pack/plugins/log_explorer/common/constants.ts @@ -13,6 +13,19 @@ export const HOST_NAME_FIELD = 'host.name'; export const LOG_LEVEL_FIELD = 'log.level'; export const MESSAGE_FIELD = 'message'; export const SERVICE_NAME_FIELD = 'service.name'; +export const TRACE_ID_FIELD = 'trace.id'; + +export const AGENT_NAME_FIELD = 'agent.name'; +export const ORCHESTRATOR_CLUSTER_NAME_FIELD = 'orchestrator.cluster.name'; +export const ORCHESTRATOR_RESOURCE_ID_FIELD = 'orchestrator.resource.id'; +export const CLOUD_PROVIDER_FIELD = 'cloud.provider'; +export const CLOUD_REGION_FIELD = 'cloud.region'; +export const CLOUD_AVAILABILITY_ZONE_FIELD = 'cloud.availability_zone'; +export const CLOUD_PROJECT_ID_FIELD = 'cloud.project.id'; +export const CLOUD_INSTANCE_ID_FIELD = 'cloud.instance.id'; +export const LOG_FILE_PATH_FIELD = 'log.file.path'; +export const DATASTREAM_NAMESPACE_FIELD = 'data_stream.namespace'; +export const DATASTREAM_DATASET_FIELD = 'data_stream.dataset'; // Sizing export const DATA_GRID_COLUMN_WIDTH_SMALL = 240; diff --git a/x-pack/plugins/log_explorer/kibana.jsonc b/x-pack/plugins/log_explorer/kibana.jsonc index 76eb47e4a59154..71781ca9cada38 100644 --- a/x-pack/plugins/log_explorer/kibana.jsonc +++ b/x-pack/plugins/log_explorer/kibana.jsonc @@ -1,7 +1,7 @@ { "type": "plugin", "id": "@kbn/log-explorer-plugin", - "owner": "@elastic/infra-monitoring-ui", + "owner": "@elastic/obs-ux-logs-team", "description": "This plugin provides a LogExplorer component using the Discover customization framework, offering several affordances specifically designed for log consumption.", "plugin": { "id": "logExplorer", diff --git a/x-pack/plugins/log_explorer/public/components/flyout_detail/flyout_detail.tsx b/x-pack/plugins/log_explorer/public/components/flyout_detail/flyout_detail.tsx index 012e5c914ed616..66c4788a32ae28 100644 --- a/x-pack/plugins/log_explorer/public/components/flyout_detail/flyout_detail.tsx +++ b/x-pack/plugins/log_explorer/public/components/flyout_detail/flyout_detail.tsx @@ -6,42 +6,22 @@ */ import React from 'react'; -import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { LogLevel } from './sub_components/log_level'; -import { Timestamp } from './sub_components/timestamp'; import { FlyoutProps, LogDocument } from './types'; -import { getDocDetailRenderFlags, useDocDetail } from './use_doc_detail'; -import { Message } from './sub_components/message'; +import { useDocDetail } from './use_doc_detail'; +import { FlyoutHeader } from './flyout_header'; +import { FlyoutHighlights } from './flyout_highlights'; -export function FlyoutDetail({ dataView, doc }: Pick) { +export function FlyoutDetail({ + dataView, + doc, + actions, +}: Pick) { const parsedDoc = useDocDetail(doc as LogDocument, { dataView }); - const { hasTimestamp, hasLogLevel, hasMessage, hasBadges, hasFlyoutHeader } = - getDocDetailRenderFlags(parsedDoc); - - return hasFlyoutHeader ? ( - - - {hasBadges && ( - - {hasLogLevel && ( - - - - )} - {hasTimestamp && ( - - - - )} - - )} - - {hasMessage && ( - - - - )} - - ) : null; + return ( + <> + + + + ); } diff --git a/x-pack/plugins/log_explorer/public/components/flyout_detail/flyout_header.tsx b/x-pack/plugins/log_explorer/public/components/flyout_detail/flyout_header.tsx new file mode 100644 index 00000000000000..babca8f2738604 --- /dev/null +++ b/x-pack/plugins/log_explorer/public/components/flyout_detail/flyout_header.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 from 'react'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { FlyoutDoc } from './types'; +import { getDocDetailHeaderRenderFlags } from './use_doc_detail'; +import { LogLevel } from './sub_components/log_level'; +import { Timestamp } from './sub_components/timestamp'; +import { Message } from './sub_components/message'; +import * as constants from '../../../common/constants'; + +export function FlyoutHeader({ doc }: { doc: FlyoutDoc }) { + const { hasTimestamp, hasLogLevel, hasMessage, hasBadges, hasFlyoutHeader } = + getDocDetailHeaderRenderFlags(doc); + + return hasFlyoutHeader ? ( + + + {hasBadges && ( + + {hasLogLevel && ( + + + + )} + {hasTimestamp && ( + + + + )} + + )} + + {hasMessage && ( + + + + )} + + ) : null; +} diff --git a/x-pack/plugins/log_explorer/public/components/flyout_detail/flyout_highlights.tsx b/x-pack/plugins/log_explorer/public/components/flyout_detail/flyout_highlights.tsx new file mode 100644 index 00000000000000..74d65d73f23b8d --- /dev/null +++ b/x-pack/plugins/log_explorer/public/components/flyout_detail/flyout_highlights.tsx @@ -0,0 +1,207 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import { FlyoutContentActions } from '@kbn/discover-plugin/public'; +import { DataTableRecord } from '@kbn/discover-utils/src/types'; +import { useMeasure } from 'react-use/lib'; +import { FlyoutDoc } from './types'; +import * as constants from '../../../common/constants'; +import { HighlightField } from './sub_components/highlight_field'; +import { + cloudAccordionTitle, + flyoutCloudAvailabilityZoneLabel, + flyoutCloudInstanceIdLabel, + flyoutCloudProjectIdLabel, + flyoutCloudProviderLabel, + flyoutCloudRegionLabel, + flyoutDatasetLabel, + flyoutHostNameLabel, + flyoutLogPathFileLabel, + flyoutNamespaceLabel, + flyoutOrchestratorClusterNameLabel, + flyoutOrchestratorResourceIdLabel, + flyoutServiceLabel, + flyoutShipperLabel, + flyoutTraceLabel, + infraAccordionTitle, + otherAccordionTitle, + serviceAccordionTitle, +} from './translations'; +import { HighlightSection } from './sub_components/highlight_section'; +import { DiscoverActionsProvider } from '../../hooks/use_discover_action'; +import { HighlightContainer } from './sub_components/highlight_container'; +import { useFlyoutColumnWidth } from '../../hooks/use_flyouot_column_width'; + +export function FlyoutHighlights({ + formattedDoc, + flattenedDoc, + actions, +}: { + formattedDoc: FlyoutDoc; + flattenedDoc: DataTableRecord['flattened']; + actions: FlyoutContentActions; +}) { + const [ref, dimensions] = useMeasure(); + const { columns, fieldWidth } = useFlyoutColumnWidth(dimensions.width); + return ( + + + + {formattedDoc[constants.SERVICE_NAME_FIELD] && ( + + )} + {formattedDoc[constants.TRACE_ID_FIELD] && ( + + )} + + + + {formattedDoc[constants.HOST_NAME_FIELD] && ( + + )} + {formattedDoc[constants.ORCHESTRATOR_CLUSTER_NAME_FIELD] && ( + + )} + {formattedDoc[constants.ORCHESTRATOR_RESOURCE_ID_FIELD] && ( + + )} + + + + {formattedDoc[constants.CLOUD_PROVIDER_FIELD] && ( + + )} + {formattedDoc[constants.CLOUD_REGION_FIELD] && ( + + )} + {formattedDoc[constants.CLOUD_AVAILABILITY_ZONE_FIELD] && ( + + )} + {formattedDoc[constants.CLOUD_PROJECT_ID_FIELD] && ( + + )} + {formattedDoc[constants.CLOUD_INSTANCE_ID_FIELD] && ( + + )} + + + + {formattedDoc[constants.LOG_FILE_PATH_FIELD] && ( + + )} + {formattedDoc[constants.DATASTREAM_NAMESPACE_FIELD] && ( + + )} + {formattedDoc[constants.DATASTREAM_DATASET_FIELD] && ( + + )} + {formattedDoc[constants.AGENT_NAME_FIELD] && ( + + )} + + + + ); +} diff --git a/x-pack/plugins/log_explorer/public/components/flyout_detail/sub_components/highlight_container.tsx b/x-pack/plugins/log_explorer/public/components/flyout_detail/sub_components/highlight_container.tsx new file mode 100644 index 00000000000000..9e00948699404e --- /dev/null +++ b/x-pack/plugins/log_explorer/public/components/flyout_detail/sub_components/highlight_container.tsx @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiHorizontalRule, EuiPanel } from '@elastic/eui'; + +interface HighlightContainerProps { + children: React.ReactNode; +} + +const hasNonUndefinedSubChild = (children: React.ReactNode[]): boolean => { + return children.some((child) => { + if (React.isValidElement(child)) { + const subChildren = React.Children.toArray(child.props.children); + return subChildren.some((subChild) => subChild !== undefined && subChild !== null); + } + return false; + }); +}; + +export const HighlightContainer = React.forwardRef( + ({ children }, ref) => { + const validChildren = React.Children.toArray(children).filter(Boolean); + const hasChildren = validChildren.length > 0; + const shouldRender = hasChildren && hasNonUndefinedSubChild(validChildren); + + const flexChildren = validChildren.map((child, idx) =>
    {child}
    ); + + return shouldRender ? ( +
    + + + {flexChildren} + +
    + ) : null; + } +); diff --git a/x-pack/plugins/log_explorer/public/components/flyout_detail/sub_components/highlight_field.tsx b/x-pack/plugins/log_explorer/public/components/flyout_detail/sub_components/highlight_field.tsx new file mode 100644 index 00000000000000..ca47b10548236d --- /dev/null +++ b/x-pack/plugins/log_explorer/public/components/flyout_detail/sub_components/highlight_field.tsx @@ -0,0 +1,103 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiFlexGroup, EuiFlexItem, EuiText, copyToClipboard } from '@elastic/eui'; +import React, { ReactNode, useMemo, useState } from 'react'; +import { HoverAction, HoverActionType } from './hover_action'; +import { + flyoutHoverActionFilterForText, + flyoutHoverActionFilterOutText, + flyoutHoverActionFilterForFieldPresentText, + flyoutHoverActionToggleColumnText, + flyoutHoverActionCopyToClipboardText, +} from '../translations'; +import { useDiscoverActionsContext } from '../../../hooks/use_discover_action'; + +interface HighlightFieldProps { + label: string | ReactNode; + field: string; + value: unknown; + formattedValue: string; + dataTestSubj: string; + width: number; +} + +export function HighlightField({ + label, + field, + value, + formattedValue, + dataTestSubj, + width, +}: HighlightFieldProps) { + const filterForText = flyoutHoverActionFilterForText(value); + const filterOutText = flyoutHoverActionFilterOutText(value); + const actions = useDiscoverActionsContext(); + const [columnAdded, setColumnAdded] = useState(false); + + const hoverActions: HoverActionType[] = useMemo( + () => [ + { + id: 'addToFilterAction', + tooltipContent: filterForText, + iconType: 'plusInCircle', + onClick: () => actions?.addFilter && actions.addFilter(field, value, '+'), + display: true, + }, + { + id: 'removeFromFilterAction', + tooltipContent: filterOutText, + iconType: 'minusInCircle', + onClick: () => actions?.addFilter && actions.addFilter(field, value, '-'), + display: true, + }, + { + id: 'filterForFieldPresentAction', + tooltipContent: flyoutHoverActionFilterForFieldPresentText, + iconType: 'filter', + onClick: () => actions?.addFilter && actions.addFilter('_exists_', field, '+'), + display: true, + }, + { + id: 'toggleColumnAction', + tooltipContent: flyoutHoverActionToggleColumnText, + iconType: 'listAdd', + onClick: () => { + if (actions) { + if (columnAdded) { + actions?.removeColumn?.(field); + } else { + actions?.addColumn?.(field); + } + setColumnAdded(!columnAdded); + } + }, + display: true, + }, + { + id: 'copyToClipboardAction', + tooltipContent: flyoutHoverActionCopyToClipboardText, + iconType: 'copyClipboard', + onClick: () => copyToClipboard(value as string), + display: true, + }, + ], + [filterForText, filterOutText, actions, field, value, columnAdded] + ); + return formattedValue ? ( + + + + {label} + + + + + + + ) : null; +} diff --git a/x-pack/plugins/log_explorer/public/components/flyout_detail/sub_components/highlight_section.tsx b/x-pack/plugins/log_explorer/public/components/flyout_detail/sub_components/highlight_section.tsx new file mode 100644 index 00000000000000..0b598339ed29b6 --- /dev/null +++ b/x-pack/plugins/log_explorer/public/components/flyout_detail/sub_components/highlight_section.tsx @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { + EuiAccordion, + EuiFlexGrid, + EuiHorizontalRule, + EuiTitle, + EuiFlexItem, + useGeneratedHtmlId, +} from '@elastic/eui'; + +interface HighlightSectionProps { + title: string; + children: React.ReactNode; + showBottomRule?: boolean; + columns: 1 | 2 | 3; +} + +export function HighlightSection({ + title, + children, + showBottomRule = true, + columns, +}: HighlightSectionProps) { + const validChildren = React.Children.toArray(children).filter(Boolean); + const shouldRenderSection = validChildren.length > 0; + const accordionTitle = ( + +

    {title}

    +
    + ); + + const flexChildren = validChildren.map((child, idx) => ( + {child} + )); + + const accordionId = useGeneratedHtmlId({ + prefix: title, + }); + + return shouldRenderSection ? ( + <> + + {flexChildren} + + {showBottomRule && } + + ) : null; +} diff --git a/x-pack/plugins/log_explorer/public/components/flyout_detail/sub_components/hover_action.tsx b/x-pack/plugins/log_explorer/public/components/flyout_detail/sub_components/hover_action.tsx new file mode 100644 index 00000000000000..5ed25be2b36d9e --- /dev/null +++ b/x-pack/plugins/log_explorer/public/components/flyout_detail/sub_components/hover_action.tsx @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { + EuiFlexGroup, + EuiToolTip, + EuiButtonIcon, + useEuiTheme, + EuiTextTruncate, + EuiText, +} from '@elastic/eui'; +import type { IconType } from '@elastic/eui'; + +export interface HoverActionType { + id: string; + tooltipContent: string; + iconType: IconType; + onClick: () => void; + display: boolean; +} + +interface HoverActionProps { + displayText: string; + actions: HoverActionType[]; + width: number; +} + +export const HoverAction = ({ displayText, actions, width }: HoverActionProps) => { + const { euiTheme } = useEuiTheme(); + + return ( + + + {(truncatedText: string) => ( + + )} + + + {actions.map((action) => ( + + action.onClick()} + /> + + ))} + + + ); +}; diff --git a/x-pack/plugins/log_explorer/public/components/flyout_detail/translations.ts b/x-pack/plugins/log_explorer/public/components/flyout_detail/translations.ts index fcb42cf79a5dd2..9c27e333a4bdcb 100644 --- a/x-pack/plugins/log_explorer/public/components/flyout_detail/translations.ts +++ b/x-pack/plugins/log_explorer/public/components/flyout_detail/translations.ts @@ -10,3 +10,151 @@ import { i18n } from '@kbn/i18n'; export const flyoutMessageLabel = i18n.translate('xpack.logExplorer.flyoutDetail.label.message', { defaultMessage: 'Message', }); + +export const flyoutServiceLabel = i18n.translate('xpack.logExplorer.flyoutDetail.label.service', { + defaultMessage: 'Service', +}); + +export const flyoutTraceLabel = i18n.translate('xpack.logExplorer.flyoutDetail.label.trace', { + defaultMessage: 'Trace', +}); + +export const flyoutHostNameLabel = i18n.translate('xpack.logExplorer.flyoutDetail.label.hostName', { + defaultMessage: 'Host name', +}); + +export const serviceAccordionTitle = i18n.translate( + 'xpack.logExplorer.flyoutDetail.accordion.title.service', + { + defaultMessage: 'Service', + } +); + +export const infraAccordionTitle = i18n.translate( + 'xpack.logExplorer.flyoutDetail.accordion.title.infrastructure', + { + defaultMessage: 'Infrastructure', + } +); + +export const cloudAccordionTitle = i18n.translate( + 'xpack.logExplorer.flyoutDetail.accordion.title.cloud', + { + defaultMessage: 'Cloud', + } +); + +export const otherAccordionTitle = i18n.translate( + 'xpack.logExplorer.flyoutDetail.accordion.title.other', + { + defaultMessage: 'Other', + } +); + +export const flyoutOrchestratorClusterNameLabel = i18n.translate( + 'xpack.logExplorer.flyoutDetail.label.orchestratorClusterName', + { + defaultMessage: 'Orchestrator cluster Name', + } +); + +export const flyoutOrchestratorResourceIdLabel = i18n.translate( + 'xpack.logExplorer.flyoutDetail.label.orchestratorResourceId', + { + defaultMessage: 'Orchestrator resource ID', + } +); + +export const flyoutCloudProviderLabel = i18n.translate( + 'xpack.logExplorer.flyoutDetail.label.cloudProvider', + { + defaultMessage: 'Cloud provider', + } +); + +export const flyoutCloudRegionLabel = i18n.translate( + 'xpack.logExplorer.flyoutDetail.label.cloudRegion', + { + defaultMessage: 'Cloud region', + } +); + +export const flyoutCloudAvailabilityZoneLabel = i18n.translate( + 'xpack.logExplorer.flyoutDetail.label.cloudAvailabilityZone', + { + defaultMessage: 'Cloud availability zone', + } +); + +export const flyoutCloudProjectIdLabel = i18n.translate( + 'xpack.logExplorer.flyoutDetail.label.cloudProjectId', + { + defaultMessage: 'Cloud project ID', + } +); + +export const flyoutCloudInstanceIdLabel = i18n.translate( + 'xpack.logExplorer.flyoutDetail.label.cloudInstanceId', + { + defaultMessage: 'Cloud instance ID', + } +); + +export const flyoutLogPathFileLabel = i18n.translate( + 'xpack.logExplorer.flyoutDetail.label.logPathFile', + { + defaultMessage: 'Log path file', + } +); + +export const flyoutNamespaceLabel = i18n.translate( + 'xpack.logExplorer.flyoutDetail.label.namespace', + { + defaultMessage: 'Namespace', + } +); + +export const flyoutDatasetLabel = i18n.translate('xpack.logExplorer.flyoutDetail.label.dataset', { + defaultMessage: 'Dataset', +}); + +export const flyoutShipperLabel = i18n.translate('xpack.logExplorer.flyoutDetail.label.shipper', { + defaultMessage: 'Shipper', +}); + +export const flyoutHoverActionFilterForText = (text: unknown) => + i18n.translate('xpack.logExplorer.flyoutDetail.value.hover.filterFor', { + defaultMessage: 'Filter for this {value}', + values: { + value: text as string, + }, + }); + +export const flyoutHoverActionFilterOutText = (text: unknown) => + i18n.translate('xpack.logExplorer.flyoutDetail.value.hover.filterOut', { + defaultMessage: 'Filter out this {value}', + values: { + value: text as string, + }, + }); + +export const flyoutHoverActionFilterForFieldPresentText = i18n.translate( + 'xpack.logExplorer.flyoutDetail.value.hover.filterForFieldPresent', + { + defaultMessage: 'Filter for field present', + } +); + +export const flyoutHoverActionToggleColumnText = i18n.translate( + 'xpack.logExplorer.flyoutDetail.value.hover.toggleColumn', + { + defaultMessage: 'Toggle column in table', + } +); + +export const flyoutHoverActionCopyToClipboardText = i18n.translate( + 'xpack.logExplorer.flyoutDetail.value.hover.copyToClipboard', + { + defaultMessage: 'Copy to clipboard', + } +); diff --git a/x-pack/plugins/log_explorer/public/components/flyout_detail/types.ts b/x-pack/plugins/log_explorer/public/components/flyout_detail/types.ts index cf8cfb8170e218..28110024108dad 100644 --- a/x-pack/plugins/log_explorer/public/components/flyout_detail/types.ts +++ b/x-pack/plugins/log_explorer/public/components/flyout_detail/types.ts @@ -5,7 +5,6 @@ * 2.0. */ -import type { EuiIconType } from '@elastic/eui/src/components/icon/icon'; import type { DataView } from '@kbn/data-views-plugin/common'; import type { FlyoutContentProps } from '@kbn/discover-plugin/public'; import type { DataTableRecord } from '@kbn/discover-utils/types'; @@ -19,6 +18,21 @@ export interface LogDocument extends DataTableRecord { '@timestamp': string; 'log.level'?: string; message?: string; + + 'host.name'?: string; + 'service.name'?: string; + 'trace.id'?: string; + 'agent.name'?: string; + 'orchestrator.cluster.name'?: string; + 'orchestrator.resource.id'?: string; + 'cloud.provider'?: string; + 'cloud.region'?: string; + 'cloud.availability_zone'?: string; + 'cloud.project.id'?: string; + 'cloud.instance.id'?: string; + 'log.file.path'?: string; + 'data_stream.namespace': string; + 'data_stream.dataset': string; }; } @@ -26,10 +40,19 @@ export interface FlyoutDoc { '@timestamp': string; 'log.level'?: string; message?: string; -} -export interface FlyoutHighlightField { - label: string; - value: string; - iconType?: EuiIconType; + 'host.name'?: string; + 'service.name'?: string; + 'trace.id'?: string; + 'agent.name'?: string; + 'orchestrator.cluster.name'?: string; + 'orchestrator.resource.id'?: string; + 'cloud.provider'?: string; + 'cloud.region'?: string; + 'cloud.availability_zone'?: string; + 'cloud.project.id'?: string; + 'cloud.instance.id'?: string; + 'log.file.path'?: string; + 'data_stream.namespace': string; + 'data_stream.dataset': string; } diff --git a/x-pack/plugins/log_explorer/public/components/flyout_detail/use_doc_detail.ts b/x-pack/plugins/log_explorer/public/components/flyout_detail/use_doc_detail.ts index 32e4bcd966745f..938855f00f5a30 100644 --- a/x-pack/plugins/log_explorer/public/components/flyout_detail/use_doc_detail.ts +++ b/x-pack/plugins/log_explorer/public/components/flyout_detail/use_doc_detail.ts @@ -5,7 +5,8 @@ * 2.0. */ import { formatFieldValue } from '@kbn/discover-utils'; -import { LOG_LEVEL_FIELD, MESSAGE_FIELD, TIMESTAMP_FIELD } from '../../../common/constants'; +import he from 'he'; +import * as constants from '../../../common/constants'; import { useKibanaContextForPlugin } from '../../utils/use_kibana'; import { FlyoutDoc, FlyoutProps, LogDocument } from './types'; @@ -30,21 +31,59 @@ export function useDocDetail( ); }; - const level = formatField(LOG_LEVEL_FIELD)?.toLowerCase(); - const timestamp = formatField(TIMESTAMP_FIELD); - const message = formatField(MESSAGE_FIELD); + // Flyout Headers + const level = formatField(constants.LOG_LEVEL_FIELD)?.toLowerCase(); + const timestamp = formatField(constants.TIMESTAMP_FIELD); + const formattedMessage = formatField(constants.MESSAGE_FIELD); + const message = formattedMessage ? he.decode(formattedMessage) : undefined; + + // Service Highlights + const serviceName = formatField(constants.SERVICE_NAME_FIELD); + const traceId = formatField(constants.TRACE_ID_FIELD); + + // Infrastructure Highlights + const hostname = formatField(constants.HOST_NAME_FIELD); + const orchestratorClusterName = formatField(constants.ORCHESTRATOR_CLUSTER_NAME_FIELD); + const orchestratorResourceId = formatField(constants.ORCHESTRATOR_RESOURCE_ID_FIELD); + + // Cloud Highlights + const cloudProvider = formatField(constants.CLOUD_PROVIDER_FIELD); + const cloudRegion = formatField(constants.CLOUD_REGION_FIELD); + const cloudAz = formatField(constants.CLOUD_AVAILABILITY_ZONE_FIELD); + const cloudProjectId = formatField(constants.CLOUD_PROJECT_ID_FIELD); + const cloudInstanceId = formatField(constants.CLOUD_INSTANCE_ID_FIELD); + + // Other Highlights + const logFilePath = formatField(constants.LOG_FILE_PATH_FIELD); + const namespace = formatField(constants.DATASTREAM_NAMESPACE_FIELD); + const dataset = formatField(constants.DATASTREAM_DATASET_FIELD); + const agentName = formatField(constants.AGENT_NAME_FIELD); return { - [LOG_LEVEL_FIELD]: level, - [TIMESTAMP_FIELD]: timestamp, - [MESSAGE_FIELD]: message, + [constants.LOG_LEVEL_FIELD]: level, + [constants.TIMESTAMP_FIELD]: timestamp, + [constants.MESSAGE_FIELD]: message, + [constants.SERVICE_NAME_FIELD]: serviceName, + [constants.TRACE_ID_FIELD]: traceId, + [constants.HOST_NAME_FIELD]: hostname, + [constants.ORCHESTRATOR_CLUSTER_NAME_FIELD]: orchestratorClusterName, + [constants.ORCHESTRATOR_RESOURCE_ID_FIELD]: orchestratorResourceId, + [constants.CLOUD_PROVIDER_FIELD]: cloudProvider, + [constants.CLOUD_REGION_FIELD]: cloudRegion, + [constants.CLOUD_AVAILABILITY_ZONE_FIELD]: cloudAz, + [constants.CLOUD_PROJECT_ID_FIELD]: cloudProjectId, + [constants.CLOUD_INSTANCE_ID_FIELD]: cloudInstanceId, + [constants.LOG_FILE_PATH_FIELD]: logFilePath, + [constants.DATASTREAM_NAMESPACE_FIELD]: namespace, + [constants.DATASTREAM_DATASET_FIELD]: dataset, + [constants.AGENT_NAME_FIELD]: agentName, }; } -export const getDocDetailRenderFlags = (doc: FlyoutDoc) => { - const hasTimestamp = Boolean(doc['@timestamp']); - const hasLogLevel = Boolean(doc['log.level']); - const hasMessage = Boolean(doc.message); +export const getDocDetailHeaderRenderFlags = (doc: FlyoutDoc) => { + const hasTimestamp = Boolean(doc[constants.TIMESTAMP_FIELD]); + const hasLogLevel = Boolean(doc[constants.LOG_LEVEL_FIELD]); + const hasMessage = Boolean(doc[constants.MESSAGE_FIELD]); const hasBadges = hasTimestamp || hasLogLevel; diff --git a/x-pack/plugins/log_explorer/public/components/log_explorer/log_explorer.tsx b/x-pack/plugins/log_explorer/public/components/log_explorer/log_explorer.tsx index 06127130852243..57736dd4b96ddd 100644 --- a/x-pack/plugins/log_explorer/public/components/log_explorer/log_explorer.tsx +++ b/x-pack/plugins/log_explorer/public/components/log_explorer/log_explorer.tsx @@ -17,6 +17,7 @@ import { createLogExplorerProfileCustomizations } from '../../customizations/log import { createPropertyGetProxy } from '../../utils/proxies'; import { LogExplorerProfileContext } from '../../state_machines/log_explorer_profile'; import { LogExplorerStartDeps } from '../../types'; +import { LogExplorerCustomizations } from './types'; export interface CreateLogExplorerArgs { core: CoreStart; @@ -29,6 +30,7 @@ export interface LogExplorerStateContainer { } export interface LogExplorerProps { + customizations?: LogExplorerCustomizations; scopedHistory: ScopedHistory; state$?: BehaviorSubject; } @@ -44,10 +46,10 @@ export const createLogExplorer = ({ core, plugins }: CreateLogExplorerArgs) => { uiSettings: createUiSettingsServiceProxy(core.uiSettings), }; - return ({ scopedHistory, state$ }: LogExplorerProps) => { + return ({ customizations = {}, scopedHistory, state$ }: LogExplorerProps) => { const logExplorerCustomizations = useMemo( - () => [createLogExplorerProfileCustomizations({ core, plugins, state$ })], - [state$] + () => [createLogExplorerProfileCustomizations({ core, customizations, plugins, state$ })], + [customizations, state$] ); return ( diff --git a/x-pack/plugins/log_explorer/public/components/log_explorer/types.ts b/x-pack/plugins/log_explorer/public/components/log_explorer/types.ts new file mode 100644 index 00000000000000..2b366cce7c55cc --- /dev/null +++ b/x-pack/plugins/log_explorer/public/components/log_explorer/types.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { DataTableRecord } from '@kbn/discover-utils/types'; + +export type RenderPreviousContent = () => React.ReactNode; + +export interface LogExplorerFlyoutContentProps { + doc: DataTableRecord; +} + +export type FlyoutRenderContent = ( + renderPreviousContent: RenderPreviousContent, + props: LogExplorerFlyoutContentProps +) => React.ReactNode; + +export interface LogExplorerCustomizations { + flyout?: { + renderContent?: FlyoutRenderContent; + }; +} diff --git a/x-pack/plugins/log_explorer/public/customizations/custom_flyout_content.tsx b/x-pack/plugins/log_explorer/public/customizations/custom_flyout_content.tsx index a4b473119744a1..e7a5b7ed35915f 100644 --- a/x-pack/plugins/log_explorer/public/customizations/custom_flyout_content.tsx +++ b/x-pack/plugins/log_explorer/public/customizations/custom_flyout_content.tsx @@ -5,10 +5,11 @@ * 2.0. */ -import React from 'react'; +import React, { useCallback } from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { FlyoutDetail } from '../components/flyout_detail/flyout_detail'; import { FlyoutProps } from '../components/flyout_detail'; +import { useLogExplorerCustomizationsContext } from '../hooks/use_log_explorer_customizations'; export const CustomFlyoutContent = ({ actions, @@ -16,12 +17,28 @@ export const CustomFlyoutContent = ({ doc, renderDefaultContent, }: FlyoutProps) => { + const { flyout } = useLogExplorerCustomizationsContext(); + + const renderPreviousContent = useCallback( + () => ( + <> + {/* Apply custom Log Explorer detail */} + + + + + ), + [actions, dataView, doc] + ); + + const content = flyout?.renderContent + ? flyout?.renderContent(renderPreviousContent, { doc }) + : renderPreviousContent(); + return ( {/* Apply custom Log Explorer detail */} - - - + {content} {/* Restore default content */} {renderDefaultContent()} diff --git a/x-pack/plugins/log_explorer/public/customizations/log_explorer_profile.tsx b/x-pack/plugins/log_explorer/public/customizations/log_explorer_profile.tsx index 85d12847529777..628ff40babc22e 100644 --- a/x-pack/plugins/log_explorer/public/customizations/log_explorer_profile.tsx +++ b/x-pack/plugins/log_explorer/public/customizations/log_explorer_profile.tsx @@ -14,6 +14,8 @@ import { LogExplorerProfileStateService } from '../state_machines/log_explorer_p import { LogExplorerStateContainer } from '../components/log_explorer'; import { LogExplorerStartDeps } from '../types'; import { useKibanaContextForPluginProvider } from '../utils/use_kibana'; +import { LogExplorerCustomizations } from '../components/log_explorer/types'; +import { LogExplorerCustomizationsProvider } from '../hooks/use_log_explorer_customizations'; const LazyCustomDatasetFilters = dynamic(() => import('./custom_dataset_filters')); const LazyCustomDatasetSelector = dynamic(() => import('./custom_dataset_selector')); @@ -21,12 +23,18 @@ const LazyCustomFlyoutContent = dynamic(() => import('./custom_flyout_content')) export interface CreateLogExplorerProfileCustomizationsDeps { core: CoreStart; + customizations: LogExplorerCustomizations; plugins: LogExplorerStartDeps; state$?: BehaviorSubject; } export const createLogExplorerProfileCustomizations = - ({ core, plugins, state$ }: CreateLogExplorerProfileCustomizationsDeps): CustomizationCallback => + ({ + core, + customizations: logExplorerCustomizations, + plugins, + state$, + }: CreateLogExplorerProfileCustomizationsDeps): CustomizationCallback => async ({ customizations, stateContainer }) => { const { data, dataViews, discover } = plugins; // Lazy load dependencies @@ -127,7 +135,9 @@ export const createLogExplorerProfileCustomizations = return ( - + + + ); }, diff --git a/x-pack/plugins/log_explorer/public/hooks/use_discover_action.ts b/x-pack/plugins/log_explorer/public/hooks/use_discover_action.ts new file mode 100644 index 00000000000000..ef1a1ae715ca66 --- /dev/null +++ b/x-pack/plugins/log_explorer/public/hooks/use_discover_action.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import createContainer from 'constate'; +import { FlyoutContentActions } from '@kbn/discover-plugin/public'; + +interface UseFlyoutActionsDeps { + value: FlyoutContentActions; +} + +const useDiscoverActions = ({ value }: UseFlyoutActionsDeps) => value; + +export const [DiscoverActionsProvider, useDiscoverActionsContext] = + createContainer(useDiscoverActions); diff --git a/x-pack/plugins/log_explorer/public/hooks/use_flyouot_column_width.tsx b/x-pack/plugins/log_explorer/public/hooks/use_flyouot_column_width.tsx new file mode 100644 index 00000000000000..53e626223b9105 --- /dev/null +++ b/x-pack/plugins/log_explorer/public/hooks/use_flyouot_column_width.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 { useEuiTheme } from '@elastic/eui'; + +interface FlyoutColumnWidth { + columns: 1 | 2 | 3; + fieldWidth: number; +} + +export const useFlyoutColumnWidth = (width: number): FlyoutColumnWidth => { + const { euiTheme } = useEuiTheme(); + + const numberOfColumns = width > euiTheme.breakpoint.m ? 3 : width > euiTheme.breakpoint.s ? 2 : 1; + const widthFactor = numberOfColumns === 3 ? 2.5 : 2.2; + const fieldWidth = width / (numberOfColumns * widthFactor); + + return { + columns: numberOfColumns, + fieldWidth, + }; +}; diff --git a/x-pack/plugins/log_explorer/public/hooks/use_log_explorer_customizations.ts b/x-pack/plugins/log_explorer/public/hooks/use_log_explorer_customizations.ts new file mode 100644 index 00000000000000..0557e17761cb43 --- /dev/null +++ b/x-pack/plugins/log_explorer/public/hooks/use_log_explorer_customizations.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import createContainer from 'constate'; +import { LogExplorerCustomizations } from '../components/log_explorer/types'; + +interface UseLogExplorerCustomizationsDeps { + value: LogExplorerCustomizations; +} + +const useLogExplorerCustomizations = ({ value }: UseLogExplorerCustomizationsDeps) => value; + +export const [LogExplorerCustomizationsProvider, useLogExplorerCustomizationsContext] = + createContainer(useLogExplorerCustomizations); diff --git a/x-pack/plugins/log_explorer/public/index.ts b/x-pack/plugins/log_explorer/public/index.ts index 00750926517e68..1ca7f37aa4c9bf 100644 --- a/x-pack/plugins/log_explorer/public/index.ts +++ b/x-pack/plugins/log_explorer/public/index.ts @@ -10,6 +10,10 @@ import type { LogExplorerConfig } from '../common/plugin_config'; import { LogExplorerPlugin } from './plugin'; export type { LogExplorerPluginSetup, LogExplorerPluginStart } from './types'; export type { LogExplorerStateContainer } from './components/log_explorer'; +export type { + LogExplorerCustomizations, + LogExplorerFlyoutContentProps, +} from './components/log_explorer/types'; export function plugin(context: PluginInitializerContext) { return new LogExplorerPlugin(context); diff --git a/x-pack/plugins/logs_shared/kibana.jsonc b/x-pack/plugins/logs_shared/kibana.jsonc index 051d1a452740e8..b78503b140a71b 100644 --- a/x-pack/plugins/logs_shared/kibana.jsonc +++ b/x-pack/plugins/logs_shared/kibana.jsonc @@ -1,7 +1,7 @@ { "type": "plugin", "id": "@kbn/logs-shared-plugin", - "owner": "@elastic/infra-monitoring-ui", + "owner": "@elastic/obs-ux-logs-team", "description": "Exposes the shared components and APIs to access and visualize logs.", "plugin": { "id": "logsShared", diff --git a/x-pack/plugins/logs_shared/public/components/log_ai_assistant/index.tsx b/x-pack/plugins/logs_shared/public/components/log_ai_assistant/index.tsx index a4df6c50cbafdc..8cf9b2da45c06a 100644 --- a/x-pack/plugins/logs_shared/public/components/log_ai_assistant/index.tsx +++ b/x-pack/plugins/logs_shared/public/components/log_ai_assistant/index.tsx @@ -4,22 +4,25 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import React from 'react'; +import React, { ComponentType } from 'react'; import { Optional } from '@kbn/utility-types'; import { dynamic } from '../../../common/dynamic'; -import type { LogAIAssistantProps } from './log_ai_assistant'; +import type { LogAIAssistantDeps } from './log_ai_assistant'; export const LogAIAssistant = dynamic(() => import('./log_ai_assistant')); interface LogAIAssistantFactoryDeps { - observabilityAIAssistant: LogAIAssistantProps['aiAssistant']; + observabilityAIAssistant: LogAIAssistantDeps['observabilityAIAssistant']; } -export function createLogAIAssistant({ observabilityAIAssistant }: LogAIAssistantFactoryDeps) { - return ({ - aiAssistant = observabilityAIAssistant, - ...props - }: Optional) => ( - +export type LogAIAssistantComponent = ComponentType< + Optional +>; + +export function createLogAIAssistant({ + observabilityAIAssistant: aiAssistant, +}: LogAIAssistantFactoryDeps): LogAIAssistantComponent { + return ({ observabilityAIAssistant = aiAssistant, ...props }) => ( + ); } diff --git a/x-pack/plugins/logs_shared/public/components/log_ai_assistant/log_ai_assistant.mock.tsx b/x-pack/plugins/logs_shared/public/components/log_ai_assistant/log_ai_assistant.mock.tsx new file mode 100644 index 00000000000000..9ece10dff81889 --- /dev/null +++ b/x-pack/plugins/logs_shared/public/components/log_ai_assistant/log_ai_assistant.mock.tsx @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +export const createLogAIAssistantMock = () => jest.fn().mockReturnValue(
    ); diff --git a/x-pack/plugins/logs_shared/public/components/log_ai_assistant/log_ai_assistant.tsx b/x-pack/plugins/logs_shared/public/components/log_ai_assistant/log_ai_assistant.tsx index bce36a6d28dc64..335a02ab3a05eb 100644 --- a/x-pack/plugins/logs_shared/public/components/log_ai_assistant/log_ai_assistant.tsx +++ b/x-pack/plugins/logs_shared/public/components/log_ai_assistant/log_ai_assistant.tsx @@ -12,6 +12,8 @@ import { type Message, ObservabilityAIAssistantPluginStart, MessageRole, + ObservabilityAIAssistantProvider, + useObservabilityAIAssistant, } from '@kbn/observability-ai-assistant-plugin/public'; import { LogEntryField } from '../../../common'; import { explainLogMessageTitle, similarLogMessagesTitle } from './translations'; @@ -21,11 +23,16 @@ export interface LogAIAssistantDocument { } export interface LogAIAssistantProps { - aiAssistant: ObservabilityAIAssistantPluginStart; doc: LogAIAssistantDocument | undefined; } -export function LogAIAssistant({ aiAssistant, doc }: LogAIAssistantProps) { +export interface LogAIAssistantDeps extends LogAIAssistantProps { + observabilityAIAssistant: ObservabilityAIAssistantPluginStart; +} + +export const LogAIAssistant = withProviders(({ doc }: LogAIAssistantProps) => { + const aiAssistant = useObservabilityAIAssistant(); + const explainLogMessageMessages = useMemo(() => { if (!doc) { return undefined; @@ -80,7 +87,20 @@ export function LogAIAssistant({ aiAssistant, doc }: LogAIAssistantProps) { ) : null} ); -} +}); // eslint-disable-next-line import/no-default-export export default LogAIAssistant; + +function withProviders(Component: React.FunctionComponent) { + return function ComponentWithProviders({ + observabilityAIAssistant, + ...props + }: LogAIAssistantDeps) { + return ( + + + + ); + }; +} diff --git a/x-pack/plugins/logs_shared/public/components/logging/log_entry_flyout/log_entry_flyout.tsx b/x-pack/plugins/logs_shared/public/components/logging/log_entry_flyout/log_entry_flyout.tsx index 62634f2aeba134..b66e864c2a4990 100644 --- a/x-pack/plugins/logs_shared/public/components/logging/log_entry_flyout/log_entry_flyout.tsx +++ b/x-pack/plugins/logs_shared/public/components/logging/log_entry_flyout/log_entry_flyout.tsx @@ -184,7 +184,7 @@ export const LogEntryFlyout = ({ > - + diff --git a/x-pack/plugins/logs_shared/public/index.ts b/x-pack/plugins/logs_shared/public/index.ts index 873d202b1af7d5..58f22fe48bccb4 100644 --- a/x-pack/plugins/logs_shared/public/index.ts +++ b/x-pack/plugins/logs_shared/public/index.ts @@ -37,6 +37,7 @@ export { useLogSummary, WithSummary } from './containers/logs/log_summary'; export { useLogEntryFlyout } from './components/logging/log_entry_flyout'; // Shared components +export type { LogAIAssistantDocument } from './components/log_ai_assistant/log_ai_assistant'; export type { LogEntryStreamItem, LogEntryColumnWidths, diff --git a/x-pack/plugins/logs_shared/public/mocks.tsx b/x-pack/plugins/logs_shared/public/mocks.tsx index 963480d8fd90fb..a9b0ebd6a6aa35 100644 --- a/x-pack/plugins/logs_shared/public/mocks.tsx +++ b/x-pack/plugins/logs_shared/public/mocks.tsx @@ -5,11 +5,13 @@ * 2.0. */ +import { createLogAIAssistantMock } from './components/log_ai_assistant/log_ai_assistant.mock'; import { createLogViewsServiceStartMock } from './services/log_views/log_views_service.mock'; import { LogsSharedClientStartExports } from './types'; export const createLogsSharedPluginStartMock = (): jest.Mocked => ({ logViews: createLogViewsServiceStartMock(), + LogAIAssistant: createLogAIAssistantMock(), }); export const _ensureTypeCompatibility = (): LogsSharedClientStartExports => diff --git a/x-pack/plugins/logs_shared/public/types.ts b/x-pack/plugins/logs_shared/public/types.ts index e67f83e4becc0c..c0379c6fc21fb4 100644 --- a/x-pack/plugins/logs_shared/public/types.ts +++ b/x-pack/plugins/logs_shared/public/types.ts @@ -17,6 +17,7 @@ import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import { ObservabilityAIAssistantPluginStart } from '@kbn/observability-ai-assistant-plugin/public'; import { UiActionsStart } from '@kbn/ui-actions-plugin/public'; +import { LogAIAssistantComponent } from './components/log_ai_assistant'; // import type { OsqueryPluginStart } from '../../osquery/public'; import { LogViewsServiceSetup, LogViewsServiceStart } from './services/log_views'; @@ -27,6 +28,7 @@ export interface LogsSharedClientSetupExports { export interface LogsSharedClientStartExports { logViews: LogViewsServiceStart; + LogAIAssistant: LogAIAssistantComponent; } // eslint-disable-next-line @typescript-eslint/no-empty-interface diff --git a/x-pack/plugins/maps/public/classes/layers/layer_group/layer_group.tsx b/x-pack/plugins/maps/public/classes/layers/layer_group/layer_group.tsx index 54ead3de1cbd6f..7b7ac229f154ed 100644 --- a/x-pack/plugins/maps/public/classes/layers/layer_group/layer_group.tsx +++ b/x-pack/plugins/maps/public/classes/layers/layer_group/layer_group.tsx @@ -284,6 +284,10 @@ export class LayerGroup implements ILayer { } isLayerLoading(zoom: number): boolean { + if (!this.isVisible()) { + return false; + } + return this._children.some((child) => { return child.isLayerLoading(zoom); }); diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/geojson_vector_layer/geojson_vector_layer.test.ts b/x-pack/plugins/maps/public/classes/layers/vector_layer/geojson_vector_layer/geojson_vector_layer.test.ts index 6717a04bc80d0d..cf558c33458ce7 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_layer/geojson_vector_layer/geojson_vector_layer.test.ts +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/geojson_vector_layer/geojson_vector_layer.test.ts @@ -75,6 +75,18 @@ describe('isLayerLoading', () => { }); describe('joins', () => { + test('should return false when layer is not visible', () => { + const layer = new GeoJsonVectorLayer({ + customIcons: [], + joins: [mockJoin], + layerDescriptor: { + visible: false, + } as unknown as VectorLayerDescriptor, + source: {} as unknown as IVectorSource, + }); + expect(layer.isLayerLoading(1)).toBe(false); + }); + describe('source data loaded with no features', () => { test('should return false when join loading has not started', () => { const layer = new GeoJsonVectorLayer({ diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/geojson_vector_layer/geojson_vector_layer.tsx b/x-pack/plugins/maps/public/classes/layers/vector_layer/geojson_vector_layer/geojson_vector_layer.tsx index 78d1ac7442a952..4faa32668f7de7 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_layer/geojson_vector_layer/geojson_vector_layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/geojson_vector_layer/geojson_vector_layer.tsx @@ -61,6 +61,10 @@ export class GeoJsonVectorLayer extends AbstractVectorLayer { } isLayerLoading(zoom: number) { + if (!this.isVisible() || !this.showAtZoomLevel(zoom)) { + return false; + } + const isSourceLoading = super.isLayerLoading(zoom); if (isSourceLoading) { return true; diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/mvt_vector_layer/mvt_vector_layer.test.tsx b/x-pack/plugins/maps/public/classes/layers/vector_layer/mvt_vector_layer/mvt_vector_layer.test.tsx index 4347452c8d22ef..69ac1a00068f21 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_layer/mvt_vector_layer/mvt_vector_layer.test.tsx +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/mvt_vector_layer/mvt_vector_layer.test.tsx @@ -110,139 +110,118 @@ describe('isLayerLoading', () => { dataRequestMetaAtStart: undefined, dataRequestToken: undefined, }; - test('should be true when tile loading has not started', () => { - const layer = new MvtVectorLayer({ - customIcons: [], - layerDescriptor: { - __dataRequests: [sourceDataRequestDescriptor], - } as unknown as VectorLayerDescriptor, - source: { - getMaxZoom: () => { - return 24; - }, - getMinZoom: () => { - return 0; - }, - } as unknown as IVectorSource, + const mockSource = { + getMaxZoom: () => { + return 24; + }, + getMinZoom: () => { + return 0; + }, + } as unknown as IVectorSource; + + describe('no joins', () => { + test('should be true when tile loading has not started', () => { + const layer = new MvtVectorLayer({ + customIcons: [], + layerDescriptor: { + __dataRequests: [sourceDataRequestDescriptor], + } as unknown as VectorLayerDescriptor, + source: mockSource, + }); + expect(layer.isLayerLoading(1)).toBe(true); }); - expect(layer.isLayerLoading(1)).toBe(true); - }); - test('should be true when tiles are loading', () => { - const layer = new MvtVectorLayer({ - customIcons: [], - layerDescriptor: { - __areTilesLoaded: false, - __dataRequests: [sourceDataRequestDescriptor], - } as unknown as VectorLayerDescriptor, - source: { - getMaxZoom: () => { - return 24; - }, - getMinZoom: () => { - return 0; - }, - } as unknown as IVectorSource, + test('should be true when tiles are loading', () => { + const layer = new MvtVectorLayer({ + customIcons: [], + layerDescriptor: { + __areTilesLoaded: false, + __dataRequests: [sourceDataRequestDescriptor], + } as unknown as VectorLayerDescriptor, + source: mockSource, + }); + expect(layer.isLayerLoading(1)).toBe(true); }); - expect(layer.isLayerLoading(1)).toBe(true); - }); - test('should be false when tiles are loaded', () => { - const layer = new MvtVectorLayer({ - customIcons: [], - layerDescriptor: { - __areTilesLoaded: true, - __dataRequests: [sourceDataRequestDescriptor], - } as unknown as VectorLayerDescriptor, - source: { - getMaxZoom: () => { - return 24; - }, - getMinZoom: () => { - return 0; - }, - } as unknown as IVectorSource, + test('should be false when tiles are loaded', () => { + const layer = new MvtVectorLayer({ + customIcons: [], + layerDescriptor: { + __areTilesLoaded: true, + __dataRequests: [sourceDataRequestDescriptor], + } as unknown as VectorLayerDescriptor, + source: mockSource, + }); + expect(layer.isLayerLoading(1)).toBe(false); }); - expect(layer.isLayerLoading(1)).toBe(false); }); - test('should be true when tiles are loaded but join is loading', () => { - const layer = new MvtVectorLayer({ - customIcons: [], - joins: [ - { - hasCompleteConfig: () => { - return true; - }, - getSourceDataRequestId: () => { - return 'join_source_a0b0da65-5e1a-4967-9dbe-74f24391afe2'; - }, - getRightJoinSource: () => { - return {} as unknown as IJoinSource; - }, - } as unknown as InnerJoin, - ], - layerDescriptor: { - __areTilesLoaded: true, - __dataRequests: [ - sourceDataRequestDescriptor, - { - dataId: 'join_source_a0b0da65-5e1a-4967-9dbe-74f24391afe2', - dataRequestMetaAtStart: {}, - dataRequestToken: Symbol('join request'), - }, - ], - } as unknown as VectorLayerDescriptor, - source: { - getMaxZoom: () => { - return 24; - }, - getMinZoom: () => { - return 0; - }, - } as unknown as IVectorSource, + describe('joins', () => { + const joinDataRequestId = 'join_source_a0b0da65-5e1a-4967-9dbe-74f24391afe2'; + const mockJoin = { + hasCompleteConfig: () => { + return true; + }, + getSourceDataRequestId: () => { + return joinDataRequestId; + }, + getRightJoinSource: () => { + return {} as unknown as IJoinSource; + }, + } as unknown as InnerJoin; + + test('should be false when layer is not visible', () => { + const layer = new MvtVectorLayer({ + customIcons: [], + joins: [mockJoin], + layerDescriptor: { + visible: false, + } as unknown as VectorLayerDescriptor, + source: mockSource, + }); + expect(layer.isLayerLoading(1)).toBe(false); + }); + + test('should be true when tiles are loaded but join is loading', () => { + const layer = new MvtVectorLayer({ + customIcons: [], + joins: [mockJoin], + layerDescriptor: { + __areTilesLoaded: true, + __dataRequests: [ + sourceDataRequestDescriptor, + { + dataId: joinDataRequestId, + dataRequestMetaAtStart: {}, + dataRequestToken: Symbol('join request'), + }, + ], + } as unknown as VectorLayerDescriptor, + source: mockSource, + }); + expect(layer.isLayerLoading(1)).toBe(true); }); - expect(layer.isLayerLoading(1)).toBe(true); - }); - test('should be false when tiles are loaded and joins are loaded', () => { - const layer = new MvtVectorLayer({ - customIcons: [], - joins: [ - { - hasCompleteConfig: () => { - return true; - }, - getSourceDataRequestId: () => { - return 'join_source_a0b0da65-5e1a-4967-9dbe-74f24391afe2'; - }, - getRightJoinSource: () => { - return {} as unknown as IJoinSource; - }, - } as unknown as InnerJoin, - ], - layerDescriptor: { - __areTilesLoaded: true, - __dataRequests: [ - sourceDataRequestDescriptor, - { - data: {}, - dataId: 'join_source_a0b0da65-5e1a-4967-9dbe-74f24391afe2', - dataRequestMeta: {}, - dataRequestMetaAtStart: undefined, - dataRequestToken: undefined, - }, - ], - } as unknown as VectorLayerDescriptor, - source: { - getMaxZoom: () => { - return 24; - }, - getMinZoom: () => { - return 0; - }, - } as unknown as IVectorSource, + test('should be false when tiles are loaded and joins are loaded', () => { + const layer = new MvtVectorLayer({ + customIcons: [], + joins: [mockJoin], + layerDescriptor: { + __areTilesLoaded: true, + __dataRequests: [ + sourceDataRequestDescriptor, + { + data: {}, + dataId: joinDataRequestId, + dataRequestMeta: {}, + dataRequestMetaAtStart: undefined, + dataRequestToken: undefined, + }, + ], + } as unknown as VectorLayerDescriptor, + source: mockSource, + }); + expect(layer.isLayerLoading(1)).toBe(false); }); - expect(layer.isLayerLoading(1)).toBe(false); }); }); diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/mvt_vector_layer/mvt_vector_layer.tsx b/x-pack/plugins/maps/public/classes/layers/vector_layer/mvt_vector_layer/mvt_vector_layer.tsx index 099edd9b9e8a3c..fa0e91258f8d2f 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_layer/mvt_vector_layer/mvt_vector_layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/mvt_vector_layer/mvt_vector_layer.tsx @@ -75,6 +75,10 @@ export class MvtVectorLayer extends AbstractVectorLayer { } isLayerLoading(zoom: number) { + if (!this.isVisible() || !this.showAtZoomLevel(zoom)) { + return false; + } + const isSourceLoading = super.isLayerLoading(zoom); return isSourceLoading ? true : this._isLoadingJoins(); } diff --git a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/__snapshots__/layer_control.test.tsx.snap b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/__snapshots__/layer_control.test.tsx.snap index 73da1b32f60ecd..20395d06745112 100644 --- a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/__snapshots__/layer_control.test.tsx.snap +++ b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/__snapshots__/layer_control.test.tsx.snap @@ -119,12 +119,8 @@ exports[`LayerControl isLayerTOCOpen Should render expand button 1`] = ` display="inlineBlock" position="left" > - @@ -138,12 +134,8 @@ exports[`LayerControl isLayerTOCOpen Should render expand button with error icon display="inlineBlock" position="left" > - @@ -157,12 +149,8 @@ exports[`LayerControl isLayerTOCOpen Should render expand button with loading ic display="inlineBlock" position="left" > - diff --git a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/expand_button.tsx b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/expand_button.tsx new file mode 100644 index 00000000000000..f80a84dc6782de --- /dev/null +++ b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/expand_button.tsx @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiButtonEmpty, EuiIcon, EuiLoadingSpinner } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +interface Props { + hasErrors: boolean; + isLoading: boolean; + onClick: () => void; +} + +export function ExpandButton({ hasErrors, isLoading, onClick }: Props) { + // isLoading indicates at least one layer is loading. + // Expand button should never be disabled. + // Not using EuiButton* with iconType props because EuiButton* disables button when isLoading prop is true. + return ( + + {isLoading ? ( +
    + +
    + ) : ( + + )} +
    + ); +} diff --git a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_control.tsx b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_control.tsx index 7a4e04066b37c7..e1c34223483c83 100644 --- a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_control.tsx +++ b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_control.tsx @@ -7,13 +7,13 @@ import React, { Fragment } from 'react'; import { + EuiButton, + EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiPanel, - EuiButton, EuiTitle, EuiSpacer, - EuiButtonIcon, EuiToolTip, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; @@ -21,6 +21,7 @@ import { i18n } from '@kbn/i18n'; import { LayerTOC } from './layer_toc'; import { isScreenshotMode } from '../../../kibana_services'; import { ILayer } from '../../../classes/layers/layer'; +import { ExpandButton } from './expand_button'; export interface Props { isReadOnly: boolean; @@ -35,32 +36,6 @@ export interface Props { zoom: number; } -function renderExpandButton({ - hasErrors, - isLoading, - onClick, -}: { - hasErrors: boolean; - isLoading: boolean; - onClick: () => void; -}) { - const expandLabel = i18n.translate('xpack.maps.layerControl.openLayerTOCButtonAriaLabel', { - defaultMessage: 'Expand layers panel', - }); - - return ( - - ); -} - export function LayerControl({ isReadOnly, isLayerTOCOpen, @@ -92,7 +67,7 @@ export function LayerControl({ })} position="left" > - {renderExpandButton({ hasErrors, isLoading, onClick: openLayerTOC })} + ); } diff --git a/x-pack/plugins/metrics_data_access/kibana.jsonc b/x-pack/plugins/metrics_data_access/kibana.jsonc index 6842ec7d4a7245..10ddf6c04e21e1 100644 --- a/x-pack/plugins/metrics_data_access/kibana.jsonc +++ b/x-pack/plugins/metrics_data_access/kibana.jsonc @@ -1,7 +1,7 @@ { "type": "plugin", "id": "@kbn/metrics-data-access-plugin", - "owner": "@elastic/infra-monitoring-ui", + "owner": "@elastic/obs-knowledge-team", "description": "Exposes utilities for accessing metrics data", "plugin": { "id": "metricsDataAccess", diff --git a/x-pack/plugins/ml/common/constants/alerts.ts b/x-pack/plugins/ml/common/constants/alerts.ts index 1b373b2ec435b3..4582f06b214b6b 100644 --- a/x-pack/plugins/ml/common/constants/alerts.ts +++ b/x-pack/plugins/ml/common/constants/alerts.ts @@ -6,6 +6,13 @@ */ import { i18n } from '@kbn/i18n'; +import { + ALERT_DURATION, + ALERT_NAMESPACE, + ALERT_RULE_NAME, + ALERT_START, + ALERT_STATUS, +} from '@kbn/rule-data-utils'; import { JobsHealthTests } from '../types/alerts'; export const ML_ALERT_TYPES = { @@ -75,3 +82,35 @@ export const HEALTH_CHECK_NAMES: Record>({ + [ALERT_RULE_NAME]: i18n.translate('xpack.ml.alertsTable.columns.ruleName', { + defaultMessage: 'Rule name', + }), + [ALERT_STATUS]: i18n.translate('xpack.ml.alertsTable.columns.status', { + defaultMessage: 'Status', + }), + [ALERT_ANOMALY_DETECTION_JOB_ID]: i18n.translate('xpack.ml.alertsTable.columns.jobId', { + defaultMessage: 'Job ID', + }), + [ALERT_ANOMALY_SCORE]: i18n.translate('xpack.ml.alertsTable.columns.anomalyScore', { + defaultMessage: 'Latest anomaly score', + }), + [ALERT_ANOMALY_TIMESTAMP]: i18n.translate('xpack.ml.alertsTable.columns.anomalyTime', { + defaultMessage: 'Latest anomaly time', + }), + [ALERT_DURATION]: i18n.translate('xpack.ml.alertsTable.columns.duration', { + defaultMessage: 'Duration', + }), + [ALERT_START]: i18n.translate('xpack.ml.alertsTable.columns.start', { + defaultMessage: 'Start time', + }), +}); diff --git a/x-pack/plugins/ml/public/alerting/anomaly_detection_alerts_table/alert_actions.tsx b/x-pack/plugins/ml/public/alerting/anomaly_detection_alerts_table/alert_actions.tsx new file mode 100644 index 00000000000000..e0206043d54427 --- /dev/null +++ b/x-pack/plugins/ml/public/alerting/anomaly_detection_alerts_table/alert_actions.tsx @@ -0,0 +1,228 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + EuiButtonIcon, + EuiContextMenuItem, + EuiContextMenuPanel, + EuiPopover, + EuiToolTip, +} from '@elastic/eui'; + +import React, { useCallback, useMemo, useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { CaseAttachmentsWithoutOwner } from '@kbn/cases-plugin/public'; +import { AttachmentType } from '@kbn/cases-plugin/common'; +import { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; +import { + ALERT_RULE_NAME, + ALERT_RULE_UUID, + ALERT_STATUS, + ALERT_STATUS_ACTIVE, + ALERT_UUID, +} from '@kbn/rule-data-utils'; +import { useBulkUntrackAlerts } from '@kbn/triggers-actions-ui-plugin/public'; +import { type Alert } from '@kbn/triggers-actions-ui-plugin/public/types'; +import { PLUGIN_ID } from '../../../common/constants/app'; +import { useMlKibana } from '../../application/contexts/kibana'; + +export interface AlertActionsProps { + alert: Alert; + ecsData: Ecs; + id?: string; + refresh: () => void; + setFlyoutAlert: React.Dispatch>; +} + +const CASES_ACTIONS_ENABLED = false; + +export function AlertActions({ + alert, + ecsData, + id: pageId, + refresh, + setFlyoutAlert, +}: AlertActionsProps) { + const alertDoc = Object.entries(alert).reduce((acc, [key, val]) => { + return { ...acc, [key]: val?.[0] }; + }, {}); + + const { + cases, + http: { + basePath: { prepend }, + }, + } = useMlKibana().services; + const casesPrivileges = cases?.helpers.canUseCases(); + + const { mutateAsync: untrackAlerts } = useBulkUntrackAlerts(); + + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + + const ruleId = alert[ALERT_RULE_UUID]?.[0] ?? null; + const alertId = alert[ALERT_UUID]?.[0] ?? ''; + + const linkToRule = ruleId + ? prepend(`/app/management/insightsAndAlerting/triggersActions/rule/${ruleId}`) + : null; + + const caseAttachments: CaseAttachmentsWithoutOwner = useMemo(() => { + return ecsData?._id + ? [ + { + alertId: alertId ?? '', + index: ecsData?._index ?? '', + type: AttachmentType.alert, + rule: { + id: ruleId, + name: alert[ALERT_RULE_NAME]![0], + }, + owner: PLUGIN_ID, + }, + ] + : []; + }, [alert, alertId, ecsData?._id, ecsData?._index, ruleId]); + + const isActiveAlert = useMemo(() => alert[ALERT_STATUS]![0] === ALERT_STATUS_ACTIVE, [alert]); + + const onSuccess = useCallback(() => { + refresh(); + }, [refresh]); + + const createCaseFlyout = cases!.hooks.useCasesAddToNewCaseFlyout({ onSuccess }); + const selectCaseModal = cases!.hooks.useCasesAddToExistingCaseModal({ onSuccess }); + + const closeActionsPopover = () => { + setIsPopoverOpen(false); + }; + + const toggleActionsPopover = () => { + setIsPopoverOpen(!isPopoverOpen); + }; + + const handleAddToNewCaseClick = () => { + createCaseFlyout.open({ attachments: caseAttachments }); + closeActionsPopover(); + }; + + const handleAddToExistingCaseClick = () => { + selectCaseModal.open({ getAttachments: () => caseAttachments }); + closeActionsPopover(); + }; + + const handleUntrackAlert = useCallback(async () => { + await untrackAlerts({ + indices: [ecsData?._index ?? ''], + alertUuids: [alertId], + }); + onSuccess(); + }, [untrackAlerts, alertId, ecsData, onSuccess]); + + const actionsMenuItems = [ + ...(CASES_ACTIONS_ENABLED && casesPrivileges?.create && casesPrivileges.read + ? [ + + {i18n.translate('xpack.ml.alerts.actions.addToCase', { + defaultMessage: 'Add to existing case', + })} + , + + {i18n.translate('xpack.ml.alerts.actions.addToNewCase', { + defaultMessage: 'Add to new case', + })} + , + ] + : []), + ...(linkToRule + ? [ + + {i18n.translate('xpack.ml.alertsTable.viewRuleDetailsButtonText', { + defaultMessage: 'View rule details', + })} + , + ] + : []), + { + closeActionsPopover(); + setFlyoutAlert({ fields: alertDoc }); + }} + > + {i18n.translate('xpack.ml.alertsTable.viewAlertDetailsButtonText', { + defaultMessage: 'View alert details', + })} + , + ...(isActiveAlert + ? [ + + {i18n.translate('xpack.ml.alerts.actions.untrack', { + defaultMessage: 'Mark as untracked', + })} + , + ] + : []), + ]; + + const actionsToolTip = + actionsMenuItems.length <= 0 + ? i18n.translate('xpack.ml.alertsTable.notEnoughPermissions', { + defaultMessage: 'Additional privileges required', + }) + : i18n.translate('xpack.ml.alertsTable.moreActionsTextLabel', { + defaultMessage: 'More actions', + }); + + return ( + <> + + + + } + closePopover={closeActionsPopover} + isOpen={isPopoverOpen} + panelPaddingSize="none" + > + + + + ); +} diff --git a/x-pack/plugins/ml/public/alerting/anomaly_detection_alerts_table/index.ts b/x-pack/plugins/ml/public/alerting/anomaly_detection_alerts_table/index.ts new file mode 100644 index 00000000000000..c44f579efe6dc2 --- /dev/null +++ b/x-pack/plugins/ml/public/alerting/anomaly_detection_alerts_table/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { registerAlertsTableConfiguration } from './register_alerts_table_configuration'; diff --git a/x-pack/plugins/ml/public/alerting/anomaly_detection_alerts_table/register_alerts_table_configuration.tsx b/x-pack/plugins/ml/public/alerting/anomaly_detection_alerts_table/register_alerts_table_configuration.tsx new file mode 100644 index 00000000000000..709bfdf1886279 --- /dev/null +++ b/x-pack/plugins/ml/public/alerting/anomaly_detection_alerts_table/register_alerts_table_configuration.tsx @@ -0,0 +1,149 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { type TriggersAndActionsUIPublicPluginSetup } from '@kbn/triggers-actions-ui-plugin/public'; +import type { + AlertsTableConfigurationRegistry, + RenderCustomActionsRowArgs, +} from '@kbn/triggers-actions-ui-plugin/public/types'; +import { EuiDataGridColumn } from '@elastic/eui'; +import { SortCombinations } from '@elastic/elasticsearch/lib/api/types'; +import type { SortOrder } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { + ALERT_DURATION, + ALERT_END, + ALERT_REASON, + ALERT_RULE_NAME, + ALERT_START, + ALERT_STATUS, +} from '@kbn/rule-data-utils'; +import type { FieldFormatsRegistry } from '@kbn/field-formats-plugin/common'; +import { getAlertFlyout } from './use_alerts_flyout'; +import { + ALERT_ANOMALY_DETECTION_JOB_ID, + ALERT_ANOMALY_SCORE, + ALERT_ANOMALY_TIMESTAMP, +} from '../../../common/constants/alerts'; +import { getAlertFormatters, getRenderCellValue } from './render_cell_value'; +import { AlertActions } from './alert_actions'; + +export function registerAlertsTableConfiguration( + triggersActionsUi: TriggersAndActionsUIPublicPluginSetup, + fieldFormats: FieldFormatsRegistry +) { + const columns: EuiDataGridColumn[] = [ + { + id: ALERT_STATUS, + displayAsText: i18n.translate('xpack.ml.alertsTable.columns.status', { + defaultMessage: 'Status', + }), + initialWidth: 150, + }, + { + id: ALERT_REASON, + displayAsText: i18n.translate('xpack.ml.alertsTable.columns.reason', { + defaultMessage: 'Reason', + }), + initialWidth: 150, + }, + { + id: ALERT_RULE_NAME, + displayAsText: i18n.translate('xpack.ml.alertsTable.columns.ruleName', { + defaultMessage: 'Rule name', + }), + initialWidth: 150, + }, + { + id: ALERT_ANOMALY_DETECTION_JOB_ID, + displayAsText: i18n.translate('xpack.ml.alertsTable.columns.jobId', { + defaultMessage: 'Job ID', + }), + initialWidth: 150, + }, + { + id: ALERT_ANOMALY_SCORE, + displayAsText: i18n.translate('xpack.ml.alertsTable.columns.anomalyScore', { + defaultMessage: 'Latest anomaly score', + }), + initialWidth: 150, + isSortable: true, + schema: 'numeric', + }, + { + id: ALERT_START, + displayAsText: i18n.translate('xpack.ml.alertsTable.columns.triggeredAt', { + defaultMessage: 'Triggered at', + }), + initialWidth: 250, + schema: 'datetime', + }, + { + id: ALERT_END, + displayAsText: i18n.translate('xpack.ml.alertsTable.columns.recoveredAt', { + defaultMessage: 'Recovered at', + }), + initialWidth: 250, + schema: 'datetime', + }, + { + id: ALERT_ANOMALY_TIMESTAMP, + displayAsText: i18n.translate('xpack.ml.alertsTable.columns.anomalyTime', { + defaultMessage: 'Latest anomaly time', + }), + initialWidth: 250, + schema: 'datetime', + }, + { + id: ALERT_DURATION, + displayAsText: i18n.translate('xpack.ml.alertsTable.columns.duration', { + defaultMessage: 'Duration', + }), + initialWidth: 150, + schema: 'numeric', + }, + ]; + + const sort: SortCombinations[] = [ + { + [ALERT_START]: { + order: 'desc' as SortOrder, + }, + }, + ]; + + const config: AlertsTableConfigurationRegistry = { + id: ML_ALERTS_CONFIG_ID, + columns, + useInternalFlyout: getAlertFlyout(columns, getAlertFormatters(fieldFormats)), + getRenderCellValue: getRenderCellValue(fieldFormats), + sort, + useActionsColumn: () => ({ + renderCustomActionsRow: ({ + alert, + id, + setFlyoutAlert, + refresh, + }: RenderCustomActionsRowArgs) => { + return ( + + ); + }, + }), + }; + + triggersActionsUi.alertsTableConfigurationRegistry.register(config); +} + +export const ML_ALERTS_CONFIG_ID = 'mlAlerts'; diff --git a/x-pack/plugins/ml/public/alerting/anomaly_detection_alerts_table/render_cell_value.tsx b/x-pack/plugins/ml/public/alerting/anomaly_detection_alerts_table/render_cell_value.tsx new file mode 100644 index 00000000000000..e58f8cd69e1b3d --- /dev/null +++ b/x-pack/plugins/ml/public/alerting/anomaly_detection_alerts_table/render_cell_value.tsx @@ -0,0 +1,115 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { isEmpty } from 'lodash'; +import React, { type ReactNode } from 'react'; +import { isDefined } from '@kbn/ml-is-defined'; +import { ALERT_DURATION, ALERT_END, ALERT_START } from '@kbn/rule-data-utils'; +import type { GetRenderCellValue } from '@kbn/triggers-actions-ui-plugin/public'; +import { FIELD_FORMAT_IDS, FieldFormatsRegistry } from '@kbn/field-formats-plugin/common'; +import { getSeverityColor } from '@kbn/ml-anomaly-utils'; +import { EuiHealth } from '@elastic/eui'; +import { + alertFieldNameMap, + ALERT_ANOMALY_SCORE, + ALERT_ANOMALY_TIMESTAMP, +} from '../../../common/constants/alerts'; +import { getFieldFormatterProvider } from '../../application/contexts/kibana/use_field_formatter'; + +interface Props { + columnId: string; + data: any; +} + +export const getMappedNonEcsValue = ({ + data, + fieldName, +}: { + data: any[]; + fieldName: string; +}): string[] | undefined => { + const item = data.find((d) => d.field === fieldName); + if (item != null && item.value != null) { + return item.value; + } + return undefined; +}; + +const getRenderValue = (mappedNonEcsValue: any) => { + // can be updated when working on https://github.com/elastic/kibana/issues/140819 + const value = Array.isArray(mappedNonEcsValue) ? mappedNonEcsValue.join() : mappedNonEcsValue; + + if (!isEmpty(value)) { + if (typeof value === 'object') { + return JSON.stringify(value); + } + return value; + } + + return '—'; +}; + +export const getRenderCellValue = (fieldFormats: FieldFormatsRegistry): GetRenderCellValue => { + const alertValueFormatter = getAlertFormatters(fieldFormats); + + return ({ setFlyoutAlert }) => + (props): ReactNode => { + const { columnId, data } = props as Props; + if (!isDefined(data)) return; + + const mappedNonEcsValue = getMappedNonEcsValue({ + data, + fieldName: columnId, + }); + const value = getRenderValue(mappedNonEcsValue); + + return alertValueFormatter(columnId, value); + }; +}; + +export function getAlertFormatters(fieldFormats: FieldFormatsRegistry) { + const getFormatter = getFieldFormatterProvider(fieldFormats); + + return (columnId: string, value: any): React.ReactElement => { + switch (columnId) { + case ALERT_START: + case ALERT_END: + case ALERT_ANOMALY_TIMESTAMP: + return <>{getFormatter(FIELD_FORMAT_IDS.DATE)(value)}; + case ALERT_DURATION: + return ( + <> + {getFormatter(FIELD_FORMAT_IDS.DURATION, { + inputFormat: 'microseconds', + outputFormat: 'humanizePrecise', + })(value)} + + ); + case ALERT_ANOMALY_SCORE: + return ( + + {getFormatter(FIELD_FORMAT_IDS.NUMBER)(value)} + + ); + default: + return <>{value}; + } + }; +} + +export function getAlertEntryFormatter(fieldFormats: FieldFormatsRegistry) { + const alertValueFormatter = getAlertFormatters(fieldFormats); + + return (columnId: string, value: any): { title: string; description: any } => { + return { + title: alertFieldNameMap[columnId], + description: alertValueFormatter(columnId, value), + }; + }; +} + +export type RegisterFormatter = ReturnType; diff --git a/x-pack/plugins/ml/public/alerting/anomaly_detection_alerts_table/use_alerts_flyout.tsx b/x-pack/plugins/ml/public/alerting/anomaly_detection_alerts_table/use_alerts_flyout.tsx new file mode 100644 index 00000000000000..71a04bd78719d5 --- /dev/null +++ b/x-pack/plugins/ml/public/alerting/anomaly_detection_alerts_table/use_alerts_flyout.tsx @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + AlertsTableFlyoutBaseProps, + AlertTableFlyoutComponent, +} from '@kbn/triggers-actions-ui-plugin/public'; +import { get } from 'lodash'; +import React from 'react'; +import { type EuiDataGridColumn, EuiDescriptionList, EuiPanel, EuiTitle } from '@elastic/eui'; +import { ALERT_RULE_NAME } from '@kbn/rule-data-utils'; +import { isDefined } from '@kbn/ml-is-defined'; +import { RegisterFormatter } from './render_cell_value'; + +const FlyoutHeader: AlertTableFlyoutComponent = ({ alert }: AlertsTableFlyoutBaseProps) => { + const name = alert[ALERT_RULE_NAME]; + return ( + +

    {name}

    +
    + ); +}; + +export const getAlertFlyout = + (columns: EuiDataGridColumn[], formatter: RegisterFormatter) => () => { + const FlyoutBody: AlertTableFlyoutComponent = ({ alert, id }: AlertsTableFlyoutBaseProps) => ( + + { + const value = get(alert, column.id)?.[0]; + + return { + title: column.displayAsText as string, + description: isDefined(value) ? formatter(column.id, value) : '—', + }; + })} + type="column" + columnWidths={[1, 3]} // Same as [25, 75] + /> + + ); + + return { + body: FlyoutBody, + header: FlyoutHeader, + footer: null, + }; + }; diff --git a/x-pack/plugins/ml/public/alerting/register_ml_alerts.ts b/x-pack/plugins/ml/public/alerting/register_ml_alerts.ts index 3379c145bf3649..9acb265bb12aff 100644 --- a/x-pack/plugins/ml/public/alerting/register_ml_alerts.ts +++ b/x-pack/plugins/ml/public/alerting/register_ml_alerts.ts @@ -9,6 +9,7 @@ import { i18n } from '@kbn/i18n'; import { lazy } from 'react'; import type { TriggersAndActionsUIPublicPluginSetup } from '@kbn/triggers-actions-ui-plugin/public'; import type { PluginSetupContract as AlertingSetup } from '@kbn/alerting-plugin/public'; +import type { MlCoreSetup } from '../plugin'; import { ML_ALERT_TYPES } from '../../common/constants/alerts'; import type { MlAnomalyDetectionAlertParams } from '../../common/types/alerts'; import { ML_APP_ROUTE, PLUGIN_ID } from '../../common/constants/app'; @@ -18,6 +19,7 @@ import { registerJobsHealthAlertingRule } from './jobs_health_rule'; export function registerMlAlerts( triggersActionsUi: TriggersAndActionsUIPublicPluginSetup, + getStartServices: MlCoreSetup['getStartServices'], alerting?: AlertingSetup ) { triggersActionsUi.ruleTypeRegistry.register({ @@ -137,6 +139,13 @@ export function registerMlAlerts( if (alerting) { registerNavigation(alerting); } + + // Async import to prevent a bundle size increase + Promise.all([getStartServices(), import('./anomaly_detection_alerts_table')]).then( + ([[_, mlStartDependencies], { registerAlertsTableConfiguration }]) => { + registerAlertsTableConfiguration(triggersActionsUi, mlStartDependencies.fieldFormats); + } + ); } export function registerNavigation(alerting: AlertingSetup) { diff --git a/x-pack/plugins/ml/public/application/aiops/change_point_detection.tsx b/x-pack/plugins/ml/public/application/aiops/change_point_detection.tsx index 551e19a2644634..3919f9f9e5ccd8 100644 --- a/x-pack/plugins/ml/public/application/aiops/change_point_detection.tsx +++ b/x-pack/plugins/ml/public/application/aiops/change_point_detection.tsx @@ -51,22 +51,22 @@ export const ChangePointDetectionPage: FC = () => { appDependencies={{ ...pick(services, [ 'application', + 'cases', + 'charts', 'data', + 'embeddable', 'executionContext', - 'charts', 'fieldFormats', 'http', + 'i18n', + 'lens', 'notifications', + 'presentationUtil', 'share', 'storage', + 'theme', 'uiSettings', 'unifiedSearch', - 'theme', - 'lens', - 'presentationUtil', - 'embeddable', - 'cases', - 'i18n', 'usageCollection', ]), fieldStats: { useFieldStatsTrigger, FieldStatsFlyoutProvider }, diff --git a/x-pack/plugins/ml/public/application/aiops/log_categorization.tsx b/x-pack/plugins/ml/public/application/aiops/log_categorization.tsx index 22fd99136d6ab3..455ff9bfc13774 100644 --- a/x-pack/plugins/ml/public/application/aiops/log_categorization.tsx +++ b/x-pack/plugins/ml/public/application/aiops/log_categorization.tsx @@ -45,19 +45,19 @@ export const LogCategorizationPage: FC = () => { showFrozenDataTierChoice={showNodeInfo} appDependencies={pick(services, [ 'application', + 'charts', 'data', 'executionContext', - 'charts', 'fieldFormats', 'http', + 'i18n', + 'lens', 'notifications', 'share', 'storage', + 'theme', 'uiSettings', 'unifiedSearch', - 'theme', - 'lens', - 'i18n', ])} /> )} diff --git a/x-pack/plugins/ml/public/application/aiops/log_rate_analysis.tsx b/x-pack/plugins/ml/public/application/aiops/log_rate_analysis.tsx index 6cc12921accada..c20264a129ea31 100644 --- a/x-pack/plugins/ml/public/application/aiops/log_rate_analysis.tsx +++ b/x-pack/plugins/ml/public/application/aiops/log_rate_analysis.tsx @@ -48,19 +48,19 @@ export const LogRateAnalysisPage: FC = () => { showFrozenDataTierChoice={showNodeInfo} appDependencies={pick(services, [ 'application', + 'charts', 'data', 'executionContext', - 'charts', 'fieldFormats', 'http', + 'i18n', + 'lens', 'notifications', 'share', 'storage', + 'theme', 'uiSettings', 'unifiedSearch', - 'theme', - 'lens', - 'i18n', ])} /> )} diff --git a/x-pack/plugins/ml/public/application/app.tsx b/x-pack/plugins/ml/public/application/app.tsx index 843f277c11eb59..449f03d81b9fde 100644 --- a/x-pack/plugins/ml/public/application/app.tsx +++ b/x-pack/plugins/ml/public/application/app.tsx @@ -32,10 +32,11 @@ import { mlApiServicesProvider } from './services/ml_api_service'; import { HttpService } from './services/http_service'; import type { PageDependencies } from './routing/router'; import { EnabledFeaturesContextProvider } from './contexts/ml'; +import type { StartServices } from './contexts/kibana'; export type MlDependencies = Omit< MlSetupDependencies, - 'share' | 'fieldFormats' | 'maps' | 'cases' | 'licensing' + 'share' | 'fieldFormats' | 'maps' | 'cases' | 'licensing' | 'uiActions' > & MlStartDependencies; @@ -78,31 +79,32 @@ const App: FC = ({ coreStart, deps, appMountParams, isServerless, mlFe setBreadcrumbs: coreStart.chrome!.setBreadcrumbs, }; - const services = useMemo(() => { + const services: StartServices = useMemo(() => { return { - kibanaVersion: deps.kibanaVersion, - share: deps.share, + cases: deps.cases, + charts: deps.charts, + contentManagement: deps.contentManagement, + dashboard: deps.dashboard, data: deps.data, dataViewEditor: deps.dataViewEditor, - security: deps.security, - licenseManagement: deps.licenseManagement, - storage: localStorage, - embeddable: deps.embeddable, - maps: deps.maps, - triggersActionsUi: deps.triggersActionsUi, + dataViews: deps.data.dataViews, dataVisualizer: deps.dataVisualizer, - usageCollection: deps.usageCollection, + embeddable: deps.embeddable, fieldFormats: deps.fieldFormats, - dashboard: deps.dashboard, - charts: deps.charts, - cases: deps.cases, - unifiedSearch: deps.unifiedSearch, - licensing: deps.licensing, + kibanaVersion: deps.kibanaVersion, lens: deps.lens, + licenseManagement: deps.licenseManagement, + maps: deps.maps, + presentationUtil: deps.presentationUtil, savedObjectsManagement: deps.savedObjectsManagement, savedSearch: deps.savedSearch, - contentManagement: deps.contentManagement, - presentationUtil: deps.presentationUtil, + security: deps.security, + share: deps.share, + storage: localStorage, + triggersActionsUi: deps.triggersActionsUi, + uiActions: deps.uiActions, + unifiedSearch: deps.unifiedSearch, + usageCollection: deps.usageCollection, ...coreStart, mlServices: getMlGlobalServices(coreStart.http, deps.usageCollection), }; diff --git a/x-pack/plugins/ml/public/application/components/chart_tooltip/index.ts b/x-pack/plugins/ml/public/application/components/chart_tooltip/index.ts index c4bbf6f8d2d7f9..d7e83511f3a152 100644 --- a/x-pack/plugins/ml/public/application/components/chart_tooltip/index.ts +++ b/x-pack/plugins/ml/public/application/components/chart_tooltip/index.ts @@ -5,5 +5,5 @@ * 2.0. */ -export { ChartTooltipService } from './chart_tooltip_service'; +export { ChartTooltipService, type TooltipData } from './chart_tooltip_service'; export { MlTooltipComponent } from './chart_tooltip'; diff --git a/x-pack/plugins/ml/public/application/components/collapsible_panel/collapsible_panel.tsx b/x-pack/plugins/ml/public/application/components/collapsible_panel/collapsible_panel.tsx index d01e676b16daf5..dbb04ddbc39f0e 100644 --- a/x-pack/plugins/ml/public/application/components/collapsible_panel/collapsible_panel.tsx +++ b/x-pack/plugins/ml/public/application/components/collapsible_panel/collapsible_panel.tsx @@ -15,7 +15,7 @@ import { EuiTitle, } from '@elastic/eui'; import React, { type FC } from 'react'; -import { css } from '@emotion/react'; +import { PanelHeaderItems } from './panel_header_items'; import { useCurrentThemeVars } from '../../contexts/kibana'; export interface CollapsiblePanelProps { @@ -67,27 +67,7 @@ export const CollapsiblePanel: FC = ({
    {headerItems ? ( - - {headerItems.map((item, i) => { - return ( - -
    - {item} -
    -
    - ); - })} -
    +
    ) : null}
    diff --git a/x-pack/plugins/ml/public/application/components/collapsible_panel/index.ts b/x-pack/plugins/ml/public/application/components/collapsible_panel/index.ts index d45a251f69ca97..0b3f9186ff6016 100644 --- a/x-pack/plugins/ml/public/application/components/collapsible_panel/index.ts +++ b/x-pack/plugins/ml/public/application/components/collapsible_panel/index.ts @@ -6,3 +6,4 @@ */ export { CollapsiblePanel } from './collapsible_panel'; +export { PanelHeaderItems } from './panel_header_items'; diff --git a/x-pack/plugins/ml/public/application/components/collapsible_panel/panel_header_items.tsx b/x-pack/plugins/ml/public/application/components/collapsible_panel/panel_header_items.tsx new file mode 100644 index 00000000000000..75d43e6ebe6f55 --- /dev/null +++ b/x-pack/plugins/ml/public/application/components/collapsible_panel/panel_header_items.tsx @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { css } from '@emotion/react'; +import React, { type FC } from 'react'; +import { useCurrentThemeVars } from '../../contexts/kibana'; + +export interface PanelHeaderItems { + headerItems: React.ReactElement[]; + compressed?: boolean; +} + +export const PanelHeaderItems: FC = ({ headerItems, compressed = false }) => { + const { euiTheme } = useCurrentThemeVars(); + + return ( + + {headerItems.map((item, i) => { + return ( + +
    + {item} +
    +
    + ); + })} +
    + ); +}; diff --git a/x-pack/plugins/ml/public/application/contexts/kibana/kibana_context.ts b/x-pack/plugins/ml/public/application/contexts/kibana/kibana_context.ts index 3e999cb1d8aa4a..5d143a55d5c277 100644 --- a/x-pack/plugins/ml/public/application/contexts/kibana/kibana_context.ts +++ b/x-pack/plugins/ml/public/application/contexts/kibana/kibana_context.ts @@ -10,7 +10,7 @@ import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import type { CoreStart } from '@kbn/core/public'; import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public'; import { useKibana, KibanaReactContextValue } from '@kbn/kibana-react-plugin/public'; -import type { SecurityPluginSetup } from '@kbn/security-plugin/public'; +import type { SecurityPluginStart } from '@kbn/security-plugin/public'; import type { LicenseManagementUIPluginSetup } from '@kbn/license-management-plugin/public'; import type { SharePluginStart } from '@kbn/share-plugin/public'; import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; @@ -18,7 +18,6 @@ import type { EmbeddableStart } from '@kbn/embeddable-plugin/public'; import type { MapsStartApi } from '@kbn/maps-plugin/public'; import type { DataVisualizerPluginStart } from '@kbn/data-visualizer-plugin/public'; import type { TriggersAndActionsUIPublicPluginStart } from '@kbn/triggers-actions-ui-plugin/public'; -import type { FieldFormatsRegistry } from '@kbn/field-formats-plugin/common'; import type { DashboardStart } from '@kbn/dashboard-plugin/public'; import type { SpacesPluginStart } from '@kbn/spaces-plugin/public'; import type { ChartsPluginStart } from '@kbn/charts-plugin/public'; @@ -30,33 +29,34 @@ import type { ContentManagementPublicStart } from '@kbn/content-management-plugi import type { SavedSearchPublicPluginStart } from '@kbn/saved-search-plugin/public'; import type { PresentationUtilPluginStart } from '@kbn/presentation-util-plugin/public'; import type { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public'; +import type { UiActionsStart } from '@kbn/ui-actions-plugin/public'; +import type { FieldFormatsRegistry } from '@kbn/field-formats-plugin/common'; import type { MlServicesContext } from '../../app'; interface StartPlugins { + cases?: CasesUiStart; + charts: ChartsPluginStart; + contentManagement: ContentManagementPublicStart; + dashboard: DashboardStart; data: DataPublicPluginStart; dataViewEditor: DataViewEditorStart; dataViews: DataViewsPublicPluginStart; - security?: SecurityPluginSetup; - licenseManagement?: LicenseManagementUIPluginSetup; - share: SharePluginStart; - embeddable: EmbeddableStart; - maps?: MapsStartApi; - triggersActionsUi?: TriggersAndActionsUIPublicPluginStart; dataVisualizer?: DataVisualizerPluginStart; - usageCollection?: UsageCollectionSetup; + embeddable: EmbeddableStart; fieldFormats: FieldFormatsRegistry; - dashboard: DashboardStart; - spacesApi?: SpacesPluginStart; - charts: ChartsPluginStart; - cases?: CasesUiStart; - unifiedSearch: UnifiedSearchPublicPluginStart; - core: CoreStart; - appName: string; lens: LensPublicStart; + licenseManagement?: LicenseManagementUIPluginSetup; + maps?: MapsStartApi; + presentationUtil: PresentationUtilPluginStart; savedObjectsManagement: SavedObjectsManagementPluginStart; savedSearch: SavedSearchPublicPluginStart; - contentManagement: ContentManagementPublicStart; - presentationUtil: PresentationUtilPluginStart; + security?: SecurityPluginStart; + share: SharePluginStart; + spacesApi?: SpacesPluginStart; + triggersActionsUi?: TriggersAndActionsUIPublicPluginStart; + uiActions: UiActionsStart; + unifiedSearch: UnifiedSearchPublicPluginStart; + usageCollection?: UsageCollectionSetup; } export type StartServices = CoreStart & StartPlugins & { diff --git a/x-pack/plugins/ml/public/application/contexts/kibana/use_field_formatter.ts b/x-pack/plugins/ml/public/application/contexts/kibana/use_field_formatter.ts index 7d34615d617589..3f417cf6f6b6ef 100644 --- a/x-pack/plugins/ml/public/application/contexts/kibana/use_field_formatter.ts +++ b/x-pack/plugins/ml/public/application/contexts/kibana/use_field_formatter.ts @@ -5,7 +5,11 @@ * 2.0. */ -import { FIELD_FORMAT_IDS, FieldFormatParams } from '@kbn/field-formats-plugin/common'; +import { + FIELD_FORMAT_IDS, + FieldFormatParams, + FieldFormatsRegistry, +} from '@kbn/field-formats-plugin/common'; import { useMlKibana } from './kibana_context'; /** @@ -16,16 +20,24 @@ const defaultParam: Record = { inputFormat: 'milliseconds', outputFormat: 'humanizePrecise', }, + [FIELD_FORMAT_IDS.NUMBER]: { + pattern: '00.00', + }, }; +export const getFieldFormatterProvider = + (fieldFormats: FieldFormatsRegistry) => + (fieldType: FIELD_FORMAT_IDS, params?: FieldFormatParams) => { + const fieldFormatter = fieldFormats.deserialize({ + id: fieldType, + params: params ?? defaultParam[fieldType], + }); + return fieldFormatter.convert.bind(fieldFormatter); + }; + export function useFieldFormatter(fieldType: FIELD_FORMAT_IDS) { const { services: { fieldFormats }, } = useMlKibana(); - - const fieldFormatter = fieldFormats.deserialize({ - id: fieldType, - params: defaultParam[fieldType], - }); - return fieldFormatter.convert.bind(fieldFormatter); + return getFieldFormatterProvider(fieldFormats)(fieldType); } diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_query_bar/exploration_query_bar.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_query_bar/exploration_query_bar.tsx index 74ca6037f8a903..e2998651f2c217 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_query_bar/exploration_query_bar.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_query_bar/exploration_query_bar.tsx @@ -19,6 +19,7 @@ import { QueryStringInput } from '@kbn/unified-search-plugin/public'; import { QueryErrorMessage } from '@kbn/ml-error-utils'; import { SEARCH_QUERY_LANGUAGE, SearchQueryLanguage } from '@kbn/ml-query-utils'; +import { PLUGIN_ID } from '../../../../../../../common/constants/app'; import { Dictionary } from '../../../../../../../common/types/common'; import { removeFilterFromQueryString } from '../../../../../explorer/explorer_utils'; import { useMlKibana } from '../../../../../contexts/kibana'; @@ -53,17 +54,8 @@ export const ExplorationQueryBar: FC = ({ ); const { services } = useMlKibana(); - const { - unifiedSearch, - data, - storage, - appName, - notifications, - http, - docLinks, - uiSettings, - dataViews, - } = services; + const { unifiedSearch, data, storage, notifications, http, docLinks, uiSettings, dataViews } = + services; const searchChangeHandler = (q: Query) => setSearchInput(q); @@ -206,7 +198,7 @@ export const ExplorationQueryBar: FC = ({ disableAutoFocus={true} dataTestSubj="mlDFAnalyticsQueryInput" languageSwitcherPopoverAnchorPosition="rightDown" - appName={appName} + appName={PLUGIN_ID} deps={{ unifiedSearch, notifications, diff --git a/x-pack/plugins/ml/public/application/explorer/alerts/alerts_panel.tsx b/x-pack/plugins/ml/public/application/explorer/alerts/alerts_panel.tsx new file mode 100644 index 00000000000000..d5a5821aae98ae --- /dev/null +++ b/x-pack/plugins/ml/public/application/explorer/alerts/alerts_panel.tsx @@ -0,0 +1,118 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + EuiButtonGroup, + EuiFlexGroup, + EuiFlexItem, + EuiNotificationBadge, + EuiSpacer, + EuiLoadingSpinner, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { ALERT_STATUS_ACTIVE, AlertConsumers, type AlertStatus } from '@kbn/rule-data-utils'; +import React, { type FC, useState } from 'react'; +import useObservable from 'react-use/lib/useObservable'; +import { ML_ALERTS_CONFIG_ID } from '../../../alerting/anomaly_detection_alerts_table/register_alerts_table_configuration'; +import { CollapsiblePanel } from '../../components/collapsible_panel'; +import { useMlKibana } from '../../contexts/kibana'; +import { useAnomalyExplorerContext } from '../anomaly_explorer_context'; +import { AlertsSummary } from './alerts_summary'; +import { AnomalyDetectionAlertsOverviewChart } from './chart'; +import { statusNameMap } from './const'; + +export const AlertsPanel: FC = () => { + const { + services: { triggersActionsUi }, + } = useMlKibana(); + + const [isOpen, setIsOpen] = useState(true); + const [toggleSelected, setToggleSelected] = useState(`alertsSummary`); + + const { anomalyDetectionAlertsStateService } = useAnomalyExplorerContext(); + + const countByStatus = useObservable(anomalyDetectionAlertsStateService.countByStatus$); + const alertsQuery = useObservable(anomalyDetectionAlertsStateService.alertsQuery$, {}); + const isLoading = useObservable(anomalyDetectionAlertsStateService.isLoading$, true); + + const alertStateProps = { + alertsTableConfigurationRegistry: triggersActionsUi!.alertsTableConfigurationRegistry, + configurationId: ML_ALERTS_CONFIG_ID, + id: `ml-details-alerts`, + featureIds: [AlertConsumers.ML], + query: alertsQuery, + showExpandToDetails: true, + showAlertStatusWithFlapping: true, + }; + const alertsStateTable = triggersActionsUi!.getAlertsStateTable(alertStateProps); + + const toggleButtons = [ + { + id: `alertsSummary`, + label: i18n.translate('xpack.ml.explorer.alertsPanel.summaryLabel', { + defaultMessage: 'Summary', + }), + }, + { + id: `alertsTable`, + label: i18n.translate('xpack.ml.explorer.alertsPanel.detailsLabel', { + defaultMessage: 'Details', + }), + }, + ]; + + return ( + <> + + + + + {isLoading ? ( + + + + ) : null} + + } + headerItems={Object.entries(countByStatus ?? {}).map(([status, count]) => { + return ( + <> + {statusNameMap[status as AlertStatus]}{' '} + + {count} + + + ); + })} + > + + + + + + + {toggleSelected === 'alertsTable' ? alertsStateTable : } + + + + ); +}; diff --git a/x-pack/plugins/ml/public/application/explorer/alerts/alerts_summary.tsx b/x-pack/plugins/ml/public/application/explorer/alerts/alerts_summary.tsx new file mode 100644 index 00000000000000..bf0469a0f31451 --- /dev/null +++ b/x-pack/plugins/ml/public/application/explorer/alerts/alerts_summary.tsx @@ -0,0 +1,128 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + EuiBadge, + EuiDescriptionList, + EuiFlexGroup, + EuiFlexItem, + EuiTitle, + EuiFlexGrid, + EuiPagination, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { ALERT_DURATION, ALERT_END } from '@kbn/rule-data-utils'; +import React, { useMemo, useState } from 'react'; +import useObservable from 'react-use/lib/useObservable'; +import { statusNameMap } from './const'; +import { getAlertFormatters } from '../../../alerting/anomaly_detection_alerts_table/render_cell_value'; +import { useMlKibana } from '../../contexts/kibana'; +import { useAnomalyExplorerContext } from '../anomaly_explorer_context'; +import { getAlertsSummary } from './get_alerts_summary'; + +const PAGE_SIZE = 3; + +export const AlertsSummary: React.FC = () => { + const { + services: { fieldFormats }, + } = useMlKibana(); + const { anomalyDetectionAlertsStateService } = useAnomalyExplorerContext(); + + const alertsData = useObservable(anomalyDetectionAlertsStateService.anomalyDetectionAlerts$, []); + const formatter = getAlertFormatters(fieldFormats); + + const [activePage, setActivePage] = useState(0); + + const sortedAlertsByRule = useMemo(() => { + return getAlertsSummary(alertsData); + }, [alertsData]); + + const pageItems = useMemo(() => { + return sortedAlertsByRule.slice(activePage * PAGE_SIZE, (activePage + 1) * PAGE_SIZE); + }, [activePage, sortedAlertsByRule]); + + return ( + <> + + {pageItems.map(([ruleName, ruleSummary]) => { + return ( + + + + +
    {ruleName}
    +
    +
    + {ruleSummary.activeCount > 0 ? ( + + {statusNameMap.active} + + ) : null} +
    + + 0 + ? [ + { + title: i18n.translate('xpack.ml.explorer.alertsPanel.summary.startedAt', { + defaultMessage: 'Started at: ', + }), + description: formatter(ALERT_END, ruleSummary.startedAt), + }, + ] + : [ + { + title: i18n.translate( + 'xpack.ml.explorer.alertsPanel.summary.recoveredAt', + { + defaultMessage: 'Recovered at: ', + } + ), + description: formatter(ALERT_END, ruleSummary.recoveredAt), + }, + ]), + { + title: i18n.translate('xpack.ml.explorer.alertsPanel.summary.lastDuration', { + defaultMessage: 'Last duration: ', + }), + description: formatter(ALERT_DURATION, ruleSummary.lastDuration), + }, + ]} + /> +
    + ); + })} +
    + {sortedAlertsByRule.length > PAGE_SIZE ? ( + + + + + + ) : null} + + ); +}; diff --git a/x-pack/plugins/ml/public/application/explorer/alerts/anomaly_detection_alerts_state_service.ts b/x-pack/plugins/ml/public/application/explorer/alerts/anomaly_detection_alerts_state_service.ts new file mode 100644 index 00000000000000..4141c582834f73 --- /dev/null +++ b/x-pack/plugins/ml/public/application/explorer/alerts/anomaly_detection_alerts_state_service.ts @@ -0,0 +1,226 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { BehaviorSubject, combineLatest, EMPTY, type Observable, Subscription } from 'rxjs'; +import { catchError, debounceTime, map, startWith, switchMap, tap } from 'rxjs/operators'; +import { + DataPublicPluginStart, + isRunningResponse, + TimefilterContract, +} from '@kbn/data-plugin/public'; +import type { + RuleRegistrySearchRequest, + RuleRegistrySearchResponse, +} from '@kbn/rule-registry-plugin/common'; +import { + ALERT_DURATION, + ALERT_END, + ALERT_RULE_NAME, + ALERT_RULE_TYPE_ID, + ALERT_START, + ALERT_STATUS, + ALERT_UUID, + AlertConsumers, +} from '@kbn/rule-data-utils'; +import { isDefined } from '@kbn/ml-is-defined'; +import { getSeverityColor } from '@kbn/ml-anomaly-utils'; +import { + ALERT_ANOMALY_DETECTION_JOB_ID, + ALERT_ANOMALY_SCORE, + ALERT_ANOMALY_TIMESTAMP, + ML_ALERT_TYPES, +} from '../../../../common/constants/alerts'; +import { StateService } from '../../services/state_service'; +import { AnomalyTimelineStateService } from '../anomaly_timeline_state_service'; + +export interface AnomalyDetectionAlert { + id: string; + [ALERT_ANOMALY_SCORE]: number; + [ALERT_ANOMALY_DETECTION_JOB_ID]: string; + [ALERT_ANOMALY_TIMESTAMP]: number; + [ALERT_START]: number; + [ALERT_END]: number | undefined; + [ALERT_RULE_NAME]: string; + [ALERT_STATUS]: string; + [ALERT_DURATION]: number; + // Additional fields for the UI + color: string; +} + +export type AlertsQuery = Exclude; + +export class AnomalyDetectionAlertsStateService extends StateService { + /** + * Subject that holds the anomaly detection alerts from the alert-as-data index. + * @private + */ + private readonly _aadAlerts$ = new BehaviorSubject([]); + + private readonly _isLoading$ = new BehaviorSubject(true); + + constructor( + private readonly _anomalyTimelineStateServices: AnomalyTimelineStateService, + private readonly data: DataPublicPluginStart, + private readonly timefilter: TimefilterContract + ) { + super(); + + this.selectedAlerts$ = combineLatest([ + this._aadAlerts$, + this._anomalyTimelineStateServices.getSelectedCells$().pipe(map((cells) => cells?.times)), + ]).pipe( + map(([alerts, selectedTimes]) => { + if (!Array.isArray(selectedTimes)) return null; + + return alerts.filter( + (alert) => + alert[ALERT_ANOMALY_TIMESTAMP] >= selectedTimes[0] * 1000 && + alert[ALERT_ANOMALY_TIMESTAMP] <= selectedTimes[1] * 1000 + ); + }) + ); + + const timeUpdates$ = this.timefilter.getTimeUpdate$().pipe( + startWith(null), + map(() => this.timefilter.getTime()) + ); + + this.alertsQuery$ = combineLatest([ + this._anomalyTimelineStateServices.getSwimLaneJobs$(), + timeUpdates$, + ]).pipe( + // Create a result query from the input + map(([selectedJobs, timeRange]) => { + return { + bool: { + filter: [ + { + term: { + [ALERT_RULE_TYPE_ID]: ML_ALERT_TYPES.ANOMALY_DETECTION, + }, + }, + { + range: { + [ALERT_ANOMALY_TIMESTAMP]: { + gte: timeRange.from, + lte: timeRange.to, + }, + }, + }, + { + terms: { + [ALERT_ANOMALY_DETECTION_JOB_ID]: selectedJobs.map((job) => job.id), + }, + }, + ], + }, + } as AlertsQuery; + }) + ); + + this._init(); + } + + /** + * Count the number of alerts by status. + * @param alerts + */ + public countAlertsByStatus(alerts: AnomalyDetectionAlert[]): Record { + return alerts.reduce( + (acc, alert) => { + if (!isDefined(acc[alert[ALERT_STATUS]])) { + acc[alert[ALERT_STATUS]] = 0; + } else { + acc[alert[ALERT_STATUS]]++; + } + return acc; + }, + { active: 0, recovered: 0 } as Record + ); + } + + public readonly anomalyDetectionAlerts$: Observable = + this._aadAlerts$.asObservable(); + + /** + * Query for fetching alerts data based on the job selection and time range. + */ + public readonly alertsQuery$: Observable; + + public readonly isLoading$: Observable = this._isLoading$.asObservable(); + + /** + * Observable for the alerts within the swim lane selection. + */ + public readonly selectedAlerts$: Observable; + + public readonly countByStatus$: Observable> = this._aadAlerts$.pipe( + map((alerts) => { + return this.countAlertsByStatus(alerts); + }) + ); + + protected _initSubscriptions(): Subscription { + const subscription = new Subscription(); + + subscription.add( + this.alertsQuery$ + .pipe( + tap(() => { + this._isLoading$.next(true); + }), + debounceTime(300), + switchMap((query) => { + return this.data.search + .search( + { + featureIds: [AlertConsumers.ML], + query, + }, + { strategy: 'privateRuleRegistryAlertsSearchStrategy' } + ) + .pipe( + catchError((error) => { + // Catch error to prevent the observable from completing + return EMPTY; + }) + ); + }) + ) + .subscribe((response) => { + if (!isRunningResponse(response)) { + this._aadAlerts$.next( + response.rawResponse.hits.hits + .map(({ fields }) => { + if (!isDefined(fields)) return; + const anomalyScore = Number(fields[ALERT_ANOMALY_SCORE][0]); + return { + id: fields[ALERT_UUID][0], + [ALERT_RULE_NAME]: fields[ALERT_RULE_NAME][0], + [ALERT_ANOMALY_SCORE]: anomalyScore, + [ALERT_ANOMALY_DETECTION_JOB_ID]: fields[ALERT_ANOMALY_DETECTION_JOB_ID][0], + [ALERT_ANOMALY_TIMESTAMP]: new Date( + fields[ALERT_ANOMALY_TIMESTAMP][0] + ).getTime(), + [ALERT_START]: fields[ALERT_START][0], + // Can be undefined if the alert is still active + [ALERT_END]: fields[ALERT_END]?.[0], + [ALERT_STATUS]: fields[ALERT_STATUS][0], + [ALERT_DURATION]: fields[ALERT_DURATION][0], + color: getSeverityColor(anomalyScore), + }; + }) + .filter(isDefined) + ); + this._isLoading$.next(false); + } + }) + ); + + return subscription; + } +} diff --git a/x-pack/plugins/ml/public/application/explorer/alerts/chart.tsx b/x-pack/plugins/ml/public/application/explorer/alerts/chart.tsx new file mode 100644 index 00000000000000..fa8b9a3de3c581 --- /dev/null +++ b/x-pack/plugins/ml/public/application/explorer/alerts/chart.tsx @@ -0,0 +1,189 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { type FC, useMemo } from 'react'; +import { useTimeRangeUpdates } from '@kbn/ml-date-picker'; +import type { TypedLensByValueInput } from '@kbn/lens-plugin/public'; +import useObservable from 'react-use/lib/useObservable'; +import { css } from '@emotion/react'; +import { i18n } from '@kbn/i18n'; +import { Y_AXIS_LABEL_WIDTH } from '../swimlane_annotation_container'; +import { useAnomalyExplorerContext } from '../anomaly_explorer_context'; +import { useMlKibana } from '../../contexts/kibana'; + +export interface AnomalyDetectionAlertsOverviewChart { + seriesType?: 'bar_stacked' | 'line'; +} + +export const AnomalyDetectionAlertsOverviewChart: FC = ({ + seriesType = 'line', +}) => { + const { + services: { + lens: { EmbeddableComponent }, + }, + } = useMlKibana(); + + const { anomalyTimelineStateService } = useAnomalyExplorerContext(); + + const timeRange = useTimeRangeUpdates(); + + const interval = useObservable( + anomalyTimelineStateService.getSwimLaneBucketInterval$(), + anomalyTimelineStateService.getSwimLaneBucketInterval() + ); + + const attributes = useMemo(() => { + return { + title: '', + visualizationType: 'lnsXY', + references: [], + type: 'lens', + state: { + internalReferences: [ + { + type: 'index-pattern', + id: 'ml-alerts-data-view', + name: 'indexpattern-datasource-layer-layer1', + }, + ], + adHocDataViews: { + 'ml-alerts-data-view': { + id: 'ml-alerts-data-view', + title: '.alerts-ml.anomaly-detection.alerts-default', + timeFieldName: '@timestamp', + }, + }, + visualization: { + hideEndzones: true, + legend: { + isVisible: false, + }, + valueLabels: 'hide', + fittingFunction: 'None', + axisTitlesVisibilitySettings: { + x: false, + yLeft: false, + yRight: false, + }, + tickLabelsVisibilitySettings: { + x: true, + yLeft: true, + yRight: true, + }, + labelsOrientation: { + x: 0, + yLeft: 0, + yRight: 0, + }, + gridlinesVisibilitySettings: { + x: true, + yLeft: true, + yRight: true, + }, + preferredSeriesType: seriesType, + layers: [ + { + layerId: 'layer1', + accessors: ['7327df72-9def-4642-a72d-dc2b0790d5f9'], + position: 'top', + seriesType, + showGridlines: false, + layerType: 'data', + xAccessor: '953f9efc-fbf6-44e0-a450-c645d2b5ec22', + }, + ], + }, + query: { + query: '', + language: 'kuery', + }, + filters: [], + datasourceStates: { + formBased: { + layers: { + layer1: { + columns: { + '953f9efc-fbf6-44e0-a450-c645d2b5ec22': { + label: '@timestamp', + dataType: 'date', + operationType: 'date_histogram', + sourceField: '@timestamp', + isBucketed: true, + scale: 'interval', + params: { + interval: interval?.expression, + includeEmptyRows: true, + dropPartials: false, + }, + }, + '7327df72-9def-4642-a72d-dc2b0790d5f9': { + label: i18n.translate('xpack.ml.explorer.alerts.totalAlerts', { + defaultMessage: 'Total alerts', + }), + dataType: 'number', + operationType: 'count', + isBucketed: false, + scale: 'ratio', + sourceField: '___records___', + params: { + emptyAsNull: false, + format: { + id: 'number', + params: { + decimals: 0, + compact: true, + }, + }, + }, + }, + }, + columnOrder: [ + '953f9efc-fbf6-44e0-a450-c645d2b5ec22', + '7327df72-9def-4642-a72d-dc2b0790d5f9', + ], + incompleteColumns: {}, + sampling: 1, + }, + }, + }, + indexpattern: { + layers: {}, + }, + textBased: { + layers: {}, + }, + }, + }, + } as TypedLensByValueInput['attributes']; + }, [interval?.expression, seriesType]); + + if (!interval) return null; + + return ( +
    + +
    + ); +}; diff --git a/x-pack/plugins/ml/public/application/explorer/alerts/const.ts b/x-pack/plugins/ml/public/application/explorer/alerts/const.ts new file mode 100644 index 00000000000000..5f2fe187a5e643 --- /dev/null +++ b/x-pack/plugins/ml/public/application/explorer/alerts/const.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { + ALERT_STATUS_ACTIVE, + ALERT_STATUS_RECOVERED, + ALERT_STATUS_UNTRACKED, +} from '@kbn/rule-data-utils'; + +export const statusNameMap = { + [ALERT_STATUS_ACTIVE]: i18n.translate('xpack.ml.explorer.alertsPanel.statusNameMap.active', { + defaultMessage: 'Active', + }), + [ALERT_STATUS_RECOVERED]: i18n.translate( + 'xpack.ml.explorer.alertsPanel.statusNameMap.recovered', + { + defaultMessage: 'Recovered', + } + ), + [ALERT_STATUS_UNTRACKED]: i18n.translate( + 'xpack.ml.explorer.alertsPanel.statusNameMap.untracked', + { + defaultMessage: 'Untracked', + } + ), +} as const; diff --git a/x-pack/plugins/ml/public/application/explorer/alerts/get_alerts_summary.test.ts b/x-pack/plugins/ml/public/application/explorer/alerts/get_alerts_summary.test.ts new file mode 100644 index 00000000000000..f8340164ddbaeb --- /dev/null +++ b/x-pack/plugins/ml/public/application/explorer/alerts/get_alerts_summary.test.ts @@ -0,0 +1,128 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + ALERT_RULE_NAME, + ALERT_STATUS, + ALERT_START, + ALERT_END, + ALERT_DURATION, +} from '@kbn/rule-data-utils'; +import { AnomalyDetectionAlert } from './anomaly_detection_alerts_state_service'; +import { getAlertsSummary } from './get_alerts_summary'; + +describe('getAlertsSummary', () => { + test('should return an empty array when given an empty array', () => { + const alertsData: AnomalyDetectionAlert[] = []; + const result = getAlertsSummary(alertsData); + expect(result).toEqual([]); + }); + + test('should group alerts by rule name and return a sorted array of rule summaries', () => { + const timestamp01 = new Date('2022-01-01T00:00:00.000Z').getTime(); + const timestamp02 = new Date('2022-01-01T01:00:00.000Z').getTime(); + const timestamp03 = new Date('2022-01-01T02:00:00.000Z').getTime(); + const timestamp04 = new Date('2022-01-01T04:00:00.000Z').getTime(); + + const alertsData: AnomalyDetectionAlert[] = [ + { + [ALERT_RULE_NAME]: 'rule-1', + [ALERT_STATUS]: 'active', + [ALERT_START]: timestamp01, + [ALERT_END]: timestamp02, + [ALERT_DURATION]: 3600000, + }, + { + [ALERT_RULE_NAME]: 'rule-1', + [ALERT_STATUS]: 'recovered', + [ALERT_START]: timestamp02, + [ALERT_END]: timestamp03, + [ALERT_DURATION]: 3600000, + }, + { + [ALERT_RULE_NAME]: 'rule-2', + [ALERT_STATUS]: 'active', + [ALERT_START]: timestamp01, + [ALERT_END]: timestamp02, + [ALERT_DURATION]: 3600000, + }, + { + [ALERT_RULE_NAME]: 'rule-2', + [ALERT_STATUS]: 'active', + [ALERT_START]: timestamp01, + [ALERT_END]: timestamp02, + [ALERT_DURATION]: 3600000, + }, + { + [ALERT_RULE_NAME]: 'rule-2', + [ALERT_STATUS]: 'recovered', + [ALERT_START]: timestamp02, + [ALERT_END]: timestamp04, + [ALERT_DURATION]: 3600000, + }, + { + [ALERT_RULE_NAME]: 'rule-3', + [ALERT_STATUS]: 'recovered', + [ALERT_START]: timestamp02, + [ALERT_END]: timestamp04, + [ALERT_DURATION]: 3600000, + }, + { + [ALERT_RULE_NAME]: 'rule-4', + [ALERT_STATUS]: 'recovered', + [ALERT_START]: timestamp02, + [ALERT_END]: timestamp04, + [ALERT_DURATION]: 6400000, + }, + ] as AnomalyDetectionAlert[]; + + const result = getAlertsSummary(alertsData); + + expect(result).toEqual([ + [ + 'rule-2', + { + totalCount: 3, + activeCount: 2, + recoveredAt: timestamp04, + startedAt: timestamp02, + lastDuration: 3600000, + }, + ], + [ + 'rule-1', + { + totalCount: 2, + activeCount: 1, + recoveredAt: timestamp03, + startedAt: timestamp02, + lastDuration: 3600000, + }, + ], + [ + 'rule-4', + { + totalCount: 1, + activeCount: 0, + recoveredAt: timestamp04, + startedAt: timestamp02, + lastDuration: 6400000, + }, + ], + [ + 'rule-3', + { + totalCount: 1, + activeCount: 0, + recoveredAt: timestamp04, + startedAt: timestamp02, + lastDuration: 3600000, + }, + ], + ]); + }); +}); diff --git a/x-pack/plugins/ml/public/application/explorer/alerts/get_alerts_summary.ts b/x-pack/plugins/ml/public/application/explorer/alerts/get_alerts_summary.ts new file mode 100644 index 00000000000000..b75a5cb3c1543e --- /dev/null +++ b/x-pack/plugins/ml/public/application/explorer/alerts/get_alerts_summary.ts @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + ALERT_DURATION, + ALERT_RULE_NAME, + ALERT_STATUS, + ALERT_END, + ALERT_START, + ALERT_STATUS_ACTIVE, +} from '@kbn/rule-data-utils'; +import { groupBy } from 'lodash'; +import { AnomalyDetectionAlert } from './anomaly_detection_alerts_state_service'; + +export type RulesSummary = Array<[string, RuleSummary]>; + +export interface RuleSummary { + activeCount: number; + totalCount: number; + lastDuration: number; + startedAt: number; + recoveredAt: number | undefined; +} + +export function getAlertsSummary(alertsData: AnomalyDetectionAlert[]): RulesSummary { + return Object.entries(groupBy(alertsData, ALERT_RULE_NAME) ?? []) + .map<[string, RuleSummary]>(([ruleName, alerts]) => { + // Find the latest alert for each rule + const latestAlert: AnomalyDetectionAlert = alerts.reduce((latest, alert) => { + return alert[ALERT_START] > latest[ALERT_START] ? alert : latest; + }); + + return [ + ruleName, + { + totalCount: alerts.length, + activeCount: alerts.filter((alert) => alert[ALERT_STATUS] === ALERT_STATUS_ACTIVE).length, + recoveredAt: latestAlert[ALERT_END], + startedAt: latestAlert[ALERT_START], + lastDuration: latestAlert[ALERT_DURATION], + }, + ]; + }) + .sort(([, alertsA], [, alertsB]) => { + // 1. Prioritize rules with the highest number of active alerts + if (alertsA.activeCount > alertsB.activeCount) return -1; + if (alertsA.activeCount < alertsB.activeCount) return 1; + // 2. Prioritize rules with the highest number of alerts in general + if (alertsA.totalCount > alertsB.totalCount) return -1; + if (alertsA.totalCount < alertsB.totalCount) return 1; + // 3. At last prioritize rules with the longest duration + if (alertsA.lastDuration > alertsB.lastDuration) return -1; + if (alertsA.lastDuration < alertsB.lastDuration) return 1; + return 0; + }); +} diff --git a/x-pack/plugins/ml/public/application/explorer/alerts/index.ts b/x-pack/plugins/ml/public/application/explorer/alerts/index.ts new file mode 100644 index 00000000000000..874724e0689906 --- /dev/null +++ b/x-pack/plugins/ml/public/application/explorer/alerts/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { AnomalyDetectionAlertsOverviewChart } from './chart'; +export { AlertsPanel } from './alerts_panel'; +export { SwimLaneWrapper } from './swim_lane_wrapper'; +export { AnomalyDetectionAlertsStateService } from './anomaly_detection_alerts_state_service'; diff --git a/x-pack/plugins/ml/public/application/explorer/alerts/swim_lane_wrapper.tsx b/x-pack/plugins/ml/public/application/explorer/alerts/swim_lane_wrapper.tsx new file mode 100644 index 00000000000000..4fefe43998f94d --- /dev/null +++ b/x-pack/plugins/ml/public/application/explorer/alerts/swim_lane_wrapper.tsx @@ -0,0 +1,281 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FormattedMessage } from '@kbn/i18n-react'; +import { + EuiButtonIcon, + EuiDescriptionList, + EuiFlexGroup, + EuiFlexItem, + EuiInMemoryTable, + EuiNotificationBadge, + EuiPopover, + EuiPopoverTitle, + EuiText, +} from '@elastic/eui'; +import { css } from '@emotion/react'; +import { + ALERT_DURATION, + ALERT_RULE_NAME, + ALERT_START, + ALERT_STATUS_ACTIVE, + type AlertStatus, +} from '@kbn/rule-data-utils'; +import { pick } from 'lodash'; +import React, { type FC, useCallback, useMemo, useRef } from 'react'; +import useObservable from 'react-use/lib/useObservable'; +import { i18n } from '@kbn/i18n'; +import { EuiBasicTableColumn } from '@elastic/eui/src/components/basic_table/basic_table'; +import { PanelHeaderItems } from '../../components/collapsible_panel'; +import { AnomalyDetectionAlert } from './anomaly_detection_alerts_state_service'; +import { + ALERT_ANOMALY_DETECTION_JOB_ID, + ALERT_ANOMALY_TIMESTAMP, + alertFieldNameMap, +} from '../../../../common/constants/alerts'; +import { + getAlertEntryFormatter, + getAlertFormatters, +} from '../../../alerting/anomaly_detection_alerts_table/render_cell_value'; +import { useMlKibana } from '../../contexts/kibana'; +import { useAnomalyExplorerContext } from '../anomaly_explorer_context'; +import type { AppStateSelectedCells, SwimlaneData } from '../explorer_utils'; +import { Y_AXIS_LABEL_WIDTH } from '../swimlane_annotation_container'; +import { CELL_HEIGHT } from '../swimlane_container'; +import { statusNameMap } from './const'; + +export interface SwimLaneWrapperProps { + selection?: AppStateSelectedCells | null; + swimlaneContainerWidth?: number; + swimLaneData: SwimlaneData; +} + +/** + * Wrapper component for the swim lane + * that handles the popover for the selected cells. + */ +export const SwimLaneWrapper: FC = ({ + children, + selection, + swimlaneContainerWidth, + swimLaneData, +}) => { + const { + services: { fieldFormats }, + } = useMlKibana(); + + const containerRef = useRef(null); + + const { anomalyDetectionAlertsStateService, anomalyTimelineStateService } = + useAnomalyExplorerContext(); + + const selectedAlerts = useObservable(anomalyDetectionAlertsStateService.selectedAlerts$, []); + + const leftOffset = useMemo(() => { + if (!selection || !swimLaneData) return 0; + const selectedCellIndex = swimLaneData.points.findIndex((v) => v.time === selection.times[0]); + const cellWidth = swimlaneContainerWidth! / swimLaneData.points.length; + + const cellOffset = (selectedCellIndex + 1) * cellWidth; + + return Y_AXIS_LABEL_WIDTH + cellOffset; + }, [selection, swimlaneContainerWidth, swimLaneData]); + + const popoverOpen = !!selection && !!selectedAlerts?.length; + + const alertFormatter = useMemo(() => getAlertEntryFormatter(fieldFormats), [fieldFormats]); + + const viewType = 'table'; + + const closePopover = useCallback(() => { + anomalyTimelineStateService.setSelectedCells(); + }, [anomalyTimelineStateService]); + + return ( +
    +
    + + } + isOpen={popoverOpen} + anchorPosition="upCenter" + hasArrow + repositionOnScroll + closePopover={closePopover} + panelPaddingSize="s" + > + + + + + + + + + { + return ( + + {statusNameMap[status as AlertStatus]}{' '} + + {count} + + + ); + })} + /> + + + + + + + + + {viewType === 'table' && !!selectedAlerts?.length ? ( + + ) : ( + (selectedAlerts ?? []).map((alert) => { + const fields = Object.entries( + pick(alert, [ + ALERT_RULE_NAME, + ALERT_ANOMALY_DETECTION_JOB_ID, + ALERT_ANOMALY_TIMESTAMP, + ALERT_START, + ALERT_DURATION, + ]) + ).map(([prop, value]) => { + return alertFormatter(prop, value); + }); + + return ( + + {fields.map(({ title, description }) => { + return ( + + + + ); + })} + + ); + }) + )} + +
    + + {children} +
    + ); +}; + +export interface MiniAlertTableProps { + data: AnomalyDetectionAlert[]; +} + +const ALERT_PER_PAGE = 3; + +export const MiniAlertTable: FC = ({ data }) => { + const { + services: { fieldFormats }, + } = useMlKibana(); + + const alertValueFormatter = useMemo(() => getAlertFormatters(fieldFormats), [fieldFormats]); + + const columns = useMemo>>(() => { + return [ + { + field: ALERT_RULE_NAME, + width: `150px`, + name: alertFieldNameMap[ALERT_RULE_NAME], + sortable: true, + }, + { + field: ALERT_START, + width: `200px`, + name: alertFieldNameMap[ALERT_START], + sortable: true, + render: (value: number) => alertValueFormatter(ALERT_START, value), + }, + { + field: ALERT_DURATION, + width: `110px`, + name: alertFieldNameMap[ALERT_DURATION], + sortable: true, + render: (value: number) => alertValueFormatter(ALERT_DURATION, value), + }, + ]; + }, [alertValueFormatter]); + + return ( + ALERT_PER_PAGE + ? { + compressed: true, + initialPageSize: ALERT_PER_PAGE, + pageSizeOptions: [3, 5, 10], + } + : false + } + /> + ); +}; diff --git a/x-pack/plugins/ml/public/application/explorer/annotation_timeline.tsx b/x-pack/plugins/ml/public/application/explorer/annotation_timeline.tsx new file mode 100644 index 00000000000000..0b95c8c3c0665b --- /dev/null +++ b/x-pack/plugins/ml/public/application/explorer/annotation_timeline.tsx @@ -0,0 +1,184 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { type FC, type PropsWithChildren, useEffect } from 'react'; +import d3 from 'd3'; +import { scaleTime } from 'd3-scale'; +import { type ChartTooltipService, type TooltipData } from '../components/chart_tooltip'; +import { useCurrentThemeVars } from '../contexts/kibana'; + +export interface AnnotationTimelineProps { + label: string; + data: T[]; + domain: { + min: number; + max: number; + }; + getTooltipContent: (item: T, hasMergedAnnotations: boolean) => TooltipData; + tooltipService: ChartTooltipService; + chartWidth: number; +} + +export const Y_AXIS_LABEL_WIDTH = 170; +export const Y_AXIS_LABEL_PADDING = 8; +const ANNOTATION_CONTAINER_HEIGHT = 12; +const ANNOTATION_MIN_WIDTH = 8; + +/** + * Reusable component for rendering annotation-like items on a timeline. + */ +export const AnnotationTimeline = ({ + data, + domain, + label, + tooltipService, + chartWidth, + getTooltipContent, +}: PropsWithChildren>): ReturnType => { + const canvasRef = React.useRef(null); + const { euiTheme } = useCurrentThemeVars(); + + useEffect( + function renderChart() { + if (!(canvasRef.current !== null && Array.isArray(data))) return; + + const chartElement = d3.select(canvasRef.current); + chartElement.selectAll('*').remove(); + + const dimensions = canvasRef.current.getBoundingClientRect(); + + const startingXPos = Y_AXIS_LABEL_WIDTH; + const endingXPos = dimensions.width; + + const svg = chartElement + .append('svg') + .attr('width', '100%') + .attr('height', ANNOTATION_CONTAINER_HEIGHT); + + const xScale = scaleTime().domain([domain.min, domain.max]).range([startingXPos, endingXPos]); + + // Add Annotation y axis label + svg + .append('text') + .attr('text-anchor', 'end') + .attr('class', 'swimlaneAnnotationLabel') + .text(label) + .attr('x', Y_AXIS_LABEL_WIDTH - Y_AXIS_LABEL_PADDING) + .attr('y', ANNOTATION_CONTAINER_HEIGHT / 2) + .attr('dominant-baseline', 'middle') + .style('fill', euiTheme.euiTextSubduedColor) + .style('font-size', euiTheme.euiFontSizeXS); + + // Add border + svg + .append('rect') + .attr('x', startingXPos) + .attr('y', 0) + .attr('height', ANNOTATION_CONTAINER_HEIGHT) + .attr('width', endingXPos - startingXPos) + .style('stroke', euiTheme.euiBorderColor) + .style('fill', 'none') + .style('stroke-width', 1); + + // Merging overlapping annotations into bigger blocks + let mergedAnnotations: Array<{ start: number; end: number; annotations: T[] }> = []; + const sortedAnnotationsData = [...data].sort((a, b) => a.timestamp - b.timestamp); + + if (sortedAnnotationsData.length > 0) { + let lastEndTime = + sortedAnnotationsData[0].end_timestamp ?? sortedAnnotationsData[0].timestamp; + + mergedAnnotations = [ + { + start: sortedAnnotationsData[0].timestamp, + end: lastEndTime, + annotations: [sortedAnnotationsData[0]], + }, + ]; + + for (let i = 1; i < sortedAnnotationsData.length; i++) { + if (sortedAnnotationsData[i].timestamp < lastEndTime) { + const itemToMerge = mergedAnnotations.pop(); + if (itemToMerge) { + const newMergedItem = { + ...itemToMerge, + end: lastEndTime, + annotations: [...itemToMerge.annotations, sortedAnnotationsData[i]], + }; + mergedAnnotations.push(newMergedItem); + } + } else { + lastEndTime = + sortedAnnotationsData[i].end_timestamp ?? sortedAnnotationsData[i].timestamp; + + mergedAnnotations.push({ + start: sortedAnnotationsData[i].timestamp, + end: lastEndTime, + annotations: [sortedAnnotationsData[i]], + }); + } + } + } + + // Add annotation marker + mergedAnnotations.forEach((mergedAnnotation) => { + const annotationWidth = Math.max( + mergedAnnotation.end + ? (xScale(Math.min(mergedAnnotation.end, domain.max)) as number) - + Math.max(xScale(mergedAnnotation.start) as number, startingXPos) + : 0, + ANNOTATION_MIN_WIDTH + ); + + const xPos = + mergedAnnotation.start >= domain.min + ? (xScale(mergedAnnotation.start) as number) + : startingXPos; + svg + .append('rect') + .classed('mlAnnotationRect', true) + // If annotation is at the end, prevent overflow by shifting it back + .attr('x', xPos + annotationWidth >= endingXPos ? endingXPos - annotationWidth : xPos) + .attr('y', 0) + .attr('height', ANNOTATION_CONTAINER_HEIGHT) + .attr('width', annotationWidth) + .on('mouseover', function (this: HTMLElement) { + let tooltipData: TooltipData = []; + if (Array.isArray(mergedAnnotation.annotations)) { + const hasMergedAnnotations = mergedAnnotation.annotations.length > 1; + if (hasMergedAnnotations) { + // @ts-ignore skipping header so it doesn't have other params + tooltipData.push({ skipHeader: true }); + } + tooltipData = [ + ...tooltipData, + ...mergedAnnotation.annotations + .map((item) => getTooltipContent(item, hasMergedAnnotations)) + .flat(), + ]; + } + + tooltipService.show(tooltipData, this); + }) + .on('mouseout', () => tooltipService.hide()); + }); + }, + [ + chartWidth, + domain, + data, + tooltipService, + label, + euiTheme.euiTextSubduedColor, + euiTheme.euiFontSizeXS, + euiTheme.euiBorderColor, + getTooltipContent, + ] + ); + + return
    ; +}; diff --git a/x-pack/plugins/ml/public/application/explorer/anomaly_explorer_context.tsx b/x-pack/plugins/ml/public/application/explorer/anomaly_explorer_context.tsx index 2217c540ccf3bb..53b9edc97cb6bd 100644 --- a/x-pack/plugins/ml/public/application/explorer/anomaly_explorer_context.tsx +++ b/x-pack/plugins/ml/public/application/explorer/anomaly_explorer_context.tsx @@ -16,6 +16,7 @@ import { useExplorerUrlState } from './hooks/use_explorer_url_state'; import { AnomalyChartsStateService } from './anomaly_charts_state_service'; import { AnomalyExplorerChartsService } from '../services/anomaly_explorer_charts_service'; import { useTableSeverity } from '../components/controls/select_severity'; +import { AnomalyDetectionAlertsStateService } from './alerts'; export type AnomalyExplorerContextValue = | { @@ -24,6 +25,7 @@ export type AnomalyExplorerContextValue = anomalyTimelineService: AnomalyTimelineService; anomalyTimelineStateService: AnomalyTimelineStateService; chartsStateService: AnomalyChartsStateService; + anomalyDetectionAlertsStateService: AnomalyDetectionAlertsStateService; } | undefined; @@ -59,6 +61,7 @@ export const AnomalyExplorerContextProvider: FC = ({ children }) => { services: { mlServices: { mlApiServices }, uiSettings, + data, }, } = useMlKibana(); @@ -102,12 +105,19 @@ export const AnomalyExplorerContextProvider: FC = ({ children }) => { tableSeverityState ); + const anomalyDetectionAlertsStateService = new AnomalyDetectionAlertsStateService( + anomalyTimelineStateService, + data, + timefilter + ); + setAnomalyExplorerContextValue({ anomalyExplorerChartsService, anomalyExplorerCommonStateService, anomalyTimelineService, anomalyTimelineStateService, chartsStateService, + anomalyDetectionAlertsStateService, }); return () => { @@ -116,6 +126,7 @@ export const AnomalyExplorerContextProvider: FC = ({ children }) => { anomalyExplorerCommonStateService.destroy(); anomalyTimelineStateService.destroy(); chartsStateService.destroy(); + anomalyDetectionAlertsStateService.destroy(); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); diff --git a/x-pack/plugins/ml/public/application/explorer/anomaly_timeline.tsx b/x-pack/plugins/ml/public/application/explorer/anomaly_timeline.tsx index 4e0416a63b34a5..9864e7b3d33054 100644 --- a/x-pack/plugins/ml/public/application/explorer/anomaly_timeline.tsx +++ b/x-pack/plugins/ml/public/application/explorer/anomaly_timeline.tsx @@ -61,6 +61,7 @@ import { AnomalyTimelineService } from '../services/anomaly_timeline_service'; import { useAnomalyExplorerContext } from './anomaly_explorer_context'; import { useTimeBuckets } from '../components/custom_hooks/use_time_buckets'; import { getTimeBoundsFromSelection } from './hooks/use_selected_cells'; +import { SwimLaneWrapper } from './alerts'; function mapSwimlaneOptionsToEuiOptions(options: string[]) { return options.map((option) => ({ @@ -506,6 +507,7 @@ export const AnomalyTimeline: FC = React.memo( + {annotationXDomain && Array.isArray(annotations) && annotations.length > 0 ? ( <> @@ -522,29 +524,35 @@ export const AnomalyTimeline: FC = React.memo( ) : null} - -
    - -
    - - } - showTimeline={false} - showLegend={false} - yAxisWidth={Y_AXIS_LABEL_WIDTH} - chartsService={chartsService} - /> + swimlaneContainerWidth={swimlaneContainerWidth} + swimLaneData={overallSwimlaneData as OverallSwimlaneData} + > + +
    + +
    + + } + showTimeline={false} + showLegend={false} + yAxisWidth={Y_AXIS_LABEL_WIDTH} + chartsService={chartsService} + /> + {viewBySwimlaneOptions.length > 0 && ( diff --git a/x-pack/plugins/ml/public/application/explorer/anomaly_timeline_state_service.ts b/x-pack/plugins/ml/public/application/explorer/anomaly_timeline_state_service.ts index 27a3836fda4714..573a0e151ae439 100644 --- a/x-pack/plugins/ml/public/application/explorer/anomaly_timeline_state_service.ts +++ b/x-pack/plugins/ml/public/application/explorer/anomaly_timeline_state_service.ts @@ -49,6 +49,12 @@ interface SwimLanePagination { viewByPerPage: number; } +export interface TimeDomain { + min: number; + max: number; + minInterval: number; +} + /** * Service for managing anomaly timeline state. */ @@ -87,6 +93,18 @@ export class AnomalyTimelineStateService extends StateService { private _timeBounds$: Observable; private _refreshSubject$: Observable; + /** Time domain of the currently active swim lane */ + public readonly timeDomain$: Observable = this._overallSwimLaneData$.pipe( + map((data) => { + if (!data) return null; + return { + min: data.earliest * 1000, + max: data.latest * 1000, + minInterval: data.interval * 1000, + }; + }) + ); + constructor( private anomalyExplorerUrlStateService: AnomalyExplorerUrlStateService, private anomalyExplorerCommonStateService: AnomalyExplorerCommonStateService, @@ -646,6 +664,42 @@ export class AnomalyTimelineStateService extends StateService { return this._viewBySwimLaneOptions$.asObservable(); } + /** + * Currently selected jobs on the swim lane + */ + public getSwimLaneJobs$(): Observable { + return combineLatest([ + this.anomalyExplorerCommonStateService.getSelectedJobs$(), + this.getViewBySwimlaneFieldName$(), + this._viewBySwimLaneData$, + this._selectedCells$, + ]).pipe( + map(([selectedJobs, swimLaneFieldName, viewBySwimLaneData, selectedCells]) => { + // If there are selected lanes on the view by swim lane, use those to filter the jobs. + if ( + selectedCells?.type === SWIMLANE_TYPE.VIEW_BY && + selectedCells?.viewByFieldName === VIEW_BY_JOB_LABEL + ) { + return selectedJobs.filter((job) => { + return selectedCells.lanes.includes(job.id); + }); + } + + if ( + selectedCells?.type === SWIMLANE_TYPE.OVERALL && + selectedCells?.viewByFieldName === VIEW_BY_JOB_LABEL && + viewBySwimLaneData + ) { + return selectedJobs.filter((job) => { + return viewBySwimLaneData.laneLabels.includes(job.id); + }); + } + + return selectedJobs; + }) + ); + } + public getViewBySwimLaneOptions(): string[] { return this._viewBySwimLaneOptions$.getValue(); } diff --git a/x-pack/plugins/ml/public/application/explorer/components/explorer_query_bar/explorer_query_bar.tsx b/x-pack/plugins/ml/public/application/explorer/components/explorer_query_bar/explorer_query_bar.tsx index a98b4dc861a361..9e55ade9f6bd58 100644 --- a/x-pack/plugins/ml/public/application/explorer/components/explorer_query_bar/explorer_query_bar.tsx +++ b/x-pack/plugins/ml/public/application/explorer/components/explorer_query_bar/explorer_query_bar.tsx @@ -15,6 +15,7 @@ import type { DataView } from '@kbn/data-views-plugin/common'; import type { QueryErrorMessage } from '@kbn/ml-error-utils'; import type { InfluencersFilterQuery } from '@kbn/ml-anomaly-utils'; import { SEARCH_QUERY_LANGUAGE } from '@kbn/ml-query-utils'; +import { PLUGIN_ID } from '../../../../../common/constants/app'; import { useAnomalyExplorerContext } from '../../anomaly_explorer_context'; import { useMlKibana } from '../../../contexts/kibana'; @@ -113,7 +114,6 @@ export const ExplorerQueryBar: FC = ({ unifiedSearch, data, storage, - appName, notifications, http, docLinks, @@ -176,7 +176,7 @@ export const ExplorerQueryBar: FC = ({ disableAutoFocus dataTestSubj="explorerQueryInput" languageSwitcherPopoverAnchorPosition="rightDown" - appName={appName} + appName={PLUGIN_ID} deps={{ unifiedSearch, notifications, diff --git a/x-pack/plugins/ml/public/application/explorer/explorer.tsx b/x-pack/plugins/ml/public/application/explorer/explorer.tsx index 28a5164719563b..7d04cfee36c668 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer.tsx +++ b/x-pack/plugins/ml/public/application/explorer/explorer.tsx @@ -78,6 +78,7 @@ import { useToastNotificationService } from '../services/toast_notification_serv import { useMlKibana, useMlLocator } from '../contexts/kibana'; import { useAnomalyExplorerContext } from './anomaly_explorer_context'; import { ML_ANOMALY_EXPLORER_PANELS } from '../../../common/types/storage'; +import { AlertsPanel } from './alerts'; interface ExplorerPageProps { jobSelectorProps: JobSelectorProps; @@ -263,8 +264,12 @@ export const Explorer: FC = ({ }, [anomalyExplorerPanelState]); const { displayWarningToast, displayDangerToast } = useToastNotificationService(); - const { anomalyTimelineStateService, anomalyExplorerCommonStateService, chartsStateService } = - useAnomalyExplorerContext(); + const { + anomalyTimelineStateService, + anomalyExplorerCommonStateService, + chartsStateService, + anomalyDetectionAlertsStateService, + } = useAnomalyExplorerContext(); const htmlIdGen = useMemo(() => htmlIdGenerator(), []); @@ -283,6 +288,8 @@ export const Explorer: FC = ({ anomalyExplorerCommonStateService.getSelectedJobs() ); + const alertsData = useObservable(anomalyDetectionAlertsStateService.anomalyDetectionAlerts$, []); + const applyFilter = useCallback( (fieldName: string, fieldValue: string, action: FilterAction) => { const { filterActive, queryString } = filterSettings; @@ -487,6 +494,8 @@ export const Explorer: FC = ({ + {alertsData.length > 0 ? : null} + {annotationsError !== undefined && ( <> diff --git a/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx b/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx index 4b69ce479c58ef..721f0958ba5fec 100644 --- a/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx +++ b/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx @@ -68,7 +68,7 @@ declare global { */ const RESIZE_THROTTLE_TIME_MS = 500; const BORDER_WIDTH = 1; -const CELL_HEIGHT = 30; +export const CELL_HEIGHT = 30; const LEGEND_HEIGHT = 34; const X_AXIS_HEIGHT = 24; diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/job_from_lens/utils.ts b/x-pack/plugins/ml/public/application/jobs/new_job/job_from_lens/utils.ts index e060017bc7fa0a..88e66aab919334 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/job_from_lens/utils.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/job_from_lens/utils.ts @@ -186,6 +186,8 @@ export async function getVisTypeFactory(lens: LensPublicStart) { export async function isCompatibleVisualizationType(chartInfo: ChartInfo) { return ( chartInfo.visualizationType === COMPATIBLE_VISUALIZATION && + // @ts-expect-error esql is missing in the type + chartInfo.query.esql === undefined && chartInfo.layers.some((l) => l.layerType === layerTypes.DATA && l.dataView !== undefined) ); } diff --git a/x-pack/plugins/ml/public/plugin.ts b/x-pack/plugins/ml/public/plugin.ts index 9b9bf6eddada22..90d107882f7bb3 100644 --- a/x-pack/plugins/ml/public/plugin.ts +++ b/x-pack/plugins/ml/public/plugin.ts @@ -42,13 +42,14 @@ import { import type { DataVisualizerPluginStart } from '@kbn/data-visualizer-plugin/public'; import type { PluginSetupContract as AlertingSetup } from '@kbn/alerting-plugin/public'; import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public'; -import type { FieldFormatsSetup, FieldFormatsStart } from '@kbn/field-formats-plugin/public'; +import type { FieldFormatsSetup } from '@kbn/field-formats-plugin/public'; import type { DashboardSetup, DashboardStart } from '@kbn/dashboard-plugin/public'; import type { ChartsPluginStart } from '@kbn/charts-plugin/public'; import type { CasesUiSetup, CasesUiStart } from '@kbn/cases-plugin/public'; import type { SavedSearchPublicPluginStart } from '@kbn/saved-search-plugin/public'; import type { PresentationUtilPluginStart } from '@kbn/presentation-util-plugin/public'; import type { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public'; +import type { FieldFormatsRegistry } from '@kbn/field-formats-plugin/common'; import { getMlSharedServices, MlSharedServices, @@ -70,45 +71,45 @@ import type { MlCapabilities } from './shared'; import { ElasticModels } from './application/services/elastic_models_service'; export interface MlStartDependencies { - dataViewEditor: DataViewEditorStart; + cases?: CasesUiStart; + charts: ChartsPluginStart; + contentManagement: ContentManagementPublicStart; + dashboard: DashboardStart; data: DataPublicPluginStart; - unifiedSearch: UnifiedSearchPublicPluginStart; - licensing: LicensingPluginStart; - share: SharePluginStart; - uiActions: UiActionsStart; - spaces?: SpacesPluginStart; + dataViewEditor: DataViewEditorStart; + dataVisualizer: DataVisualizerPluginStart; embeddable: EmbeddableStart; + fieldFormats: FieldFormatsRegistry; + lens: LensPublicStart; + licensing: LicensingPluginStart; maps?: MapsStartApi; - triggersActionsUi?: TriggersAndActionsUIPublicPluginStart; - dataVisualizer: DataVisualizerPluginStart; - fieldFormats: FieldFormatsStart; - dashboard: DashboardStart; - charts: ChartsPluginStart; - lens?: LensPublicStart; - cases?: CasesUiStart; - security: SecurityPluginStart; + presentationUtil: PresentationUtilPluginStart; savedObjectsManagement: SavedObjectsManagementPluginStart; savedSearch: SavedSearchPublicPluginStart; - contentManagement: ContentManagementPublicStart; - presentationUtil: PresentationUtilPluginStart; + security: SecurityPluginStart; + share: SharePluginStart; + spaces?: SpacesPluginStart; + triggersActionsUi?: TriggersAndActionsUIPublicPluginStart; + uiActions: UiActionsStart; + unifiedSearch: UnifiedSearchPublicPluginStart; } export interface MlSetupDependencies { - maps?: MapsSetupApi; - licensing: LicensingPluginSetup; - management?: ManagementSetup; - licenseManagement?: LicenseManagementUIPluginSetup; - home?: HomePublicPluginSetup; + alerting?: AlertingSetup; + cases?: CasesUiSetup; + dashboard: DashboardSetup; embeddable: EmbeddableSetup; - uiActions: UiActionsSetup; + fieldFormats: FieldFormatsSetup; + home?: HomePublicPluginSetup; kibanaVersion: string; + licenseManagement?: LicenseManagementUIPluginSetup; + licensing: LicensingPluginSetup; + management?: ManagementSetup; + maps?: MapsSetupApi; share: SharePluginSetup; triggersActionsUi?: TriggersAndActionsUIPublicPluginSetup; - alerting?: AlertingSetup; + uiActions: UiActionsSetup; usageCollection?: UsageCollectionSetup; - fieldFormats: FieldFormatsSetup; - dashboard: DashboardSetup; - cases?: CasesUiSetup; } export type MlCoreSetup = CoreSetup; @@ -154,31 +155,31 @@ export class MlPlugin implements Plugin { return renderApp( coreStart, { + cases: pluginsStart.cases, charts: pluginsStart.charts, + contentManagement: pluginsStart.contentManagement, + dashboard: pluginsStart.dashboard, data: pluginsStart.data, dataViewEditor: pluginsStart.dataViewEditor, - unifiedSearch: pluginsStart.unifiedSearch, - dashboard: pluginsStart.dashboard, - share: pluginsStart.share, - security: pluginsStart.security, - licensing: pluginsStart.licensing, - management: pluginsSetup.management, - licenseManagement: pluginsSetup.licenseManagement, - home: pluginsSetup.home, - embeddable: { ...pluginsSetup.embeddable, ...pluginsStart.embeddable }, - maps: pluginsStart.maps, - uiActions: pluginsStart.uiActions, - kibanaVersion: this.initializerContext.env.packageInfo.version, - triggersActionsUi: pluginsStart.triggersActionsUi, dataVisualizer: pluginsStart.dataVisualizer, - usageCollection: pluginsSetup.usageCollection, + embeddable: { ...pluginsSetup.embeddable, ...pluginsStart.embeddable }, fieldFormats: pluginsStart.fieldFormats, + home: pluginsSetup.home, + kibanaVersion: this.initializerContext.env.packageInfo.version, lens: pluginsStart.lens, - cases: pluginsStart.cases, + licenseManagement: pluginsSetup.licenseManagement, + licensing: pluginsStart.licensing, + management: pluginsSetup.management, + maps: pluginsStart.maps, + presentationUtil: pluginsStart.presentationUtil, savedObjectsManagement: pluginsStart.savedObjectsManagement, savedSearch: pluginsStart.savedSearch, - contentManagement: pluginsStart.contentManagement, - presentationUtil: pluginsStart.presentationUtil, + security: pluginsStart.security, + share: pluginsStart.share, + triggersActionsUi: pluginsStart.triggersActionsUi, + uiActions: pluginsStart.uiActions, + unifiedSearch: pluginsStart.unifiedSearch, + usageCollection: pluginsSetup.usageCollection, }, params, this.isServerless, @@ -246,7 +247,11 @@ export class MlPlugin implements Plugin { mlCapabilities.canUseMlAlerts && mlCapabilities.canGetJobs ) { - registerMlAlerts(pluginsSetup.triggersActionsUi, pluginsSetup.alerting); + registerMlAlerts( + pluginsSetup.triggersActionsUi, + core.getStartServices, + pluginsSetup.alerting + ); } if (pluginsSetup.maps) { diff --git a/x-pack/plugins/ml/server/lib/alerts/alerting_service.ts b/x-pack/plugins/ml/server/lib/alerts/alerting_service.ts index dc85428a63386a..2a52e1d1337e27 100644 --- a/x-pack/plugins/ml/server/lib/alerts/alerting_service.ts +++ b/x-pack/plugins/ml/server/lib/alerts/alerting_service.ts @@ -928,7 +928,7 @@ export function alertingServiceProvider( spaceId ); - const message = i18n.translate( + const contextMessage = i18n.translate( 'xpack.ml.alertTypes.anomalyDetectionAlertingRule.recoveredMessage', { defaultMessage: @@ -940,18 +940,26 @@ export function alertingServiceProvider( } ); + const payloadMessage = i18n.translate( + 'xpack.ml.alertTypes.anomalyDetectionAlertingRule.recoveredReason', + { + defaultMessage: + 'No anomalies have been found in the concecutive bucket after the alert was triggered.', + } + ); + return { name: '', isHealthy: true, payload: { [ALERT_URL]: url, - [ALERT_REASON]: message, + [ALERT_REASON]: payloadMessage, job_id: queryParams.jobIds[0], }, context: { anomalyExplorerUrl: url, jobIds: queryParams.jobIds, - message, + message: contextMessage, } as AnomalyDetectionAlertContext, }; } 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 8e0abc16821fe6..ad536027a6619c 100644 --- a/x-pack/plugins/ml/server/lib/alerts/register_anomaly_detection_alert_type.ts +++ b/x-pack/plugins/ml/server/lib/alerts/register_anomaly_detection_alert_type.ts @@ -6,20 +6,28 @@ */ import { i18n } from '@kbn/i18n'; -import { KibanaRequest, DEFAULT_APP_CATEGORIES } from '@kbn/core/server'; +import { DEFAULT_APP_CATEGORIES, KibanaRequest } from '@kbn/core/server'; import type { ActionGroup, AlertInstanceContext, AlertInstanceState, + RecoveredActionGroupId, RuleTypeParams, RuleTypeState, - RecoveredActionGroupId, } from '@kbn/alerting-plugin/common'; import { IRuleTypeAlerts, RuleExecutorOptions } from '@kbn/alerting-plugin/server'; -import { ALERT_NAMESPACE, ALERT_REASON, ALERT_URL } from '@kbn/rule-data-utils'; +import { ALERT_REASON, ALERT_URL } from '@kbn/rule-data-utils'; import { MlAnomalyDetectionAlert } from '@kbn/alerts-as-data-utils'; import { ES_FIELD_TYPES } from '@kbn/field-types'; -import { ML_ALERT_TYPES } from '../../../common/constants/alerts'; +import { + ALERT_ANOMALY_DETECTION_JOB_ID, + ALERT_ANOMALY_IS_INTERIM, + ALERT_ANOMALY_SCORE, + ALERT_ANOMALY_TIMESTAMP, + ALERT_TOP_INFLUENCERS, + ALERT_TOP_RECORDS, + ML_ALERT_TYPES, +} from '../../../common/constants/alerts'; import { PLUGIN_ID } from '../../../common/constants/app'; import { MINIMUM_FULL_LICENSE } from '../../../common/license'; import { @@ -79,17 +87,6 @@ export type AnomalyScoreMatchGroupId = typeof ANOMALY_SCORE_MATCH_GROUP_ID; export const ANOMALY_DETECTION_AAD_INDEX_NAME = 'ml.anomaly-detection'; -const ML_ALERT_NAMESPACE = ALERT_NAMESPACE; - -export const ALERT_ANOMALY_DETECTION_JOB_ID = `${ML_ALERT_NAMESPACE}.job_id` as const; - -export const ALERT_ANOMALY_SCORE = `${ML_ALERT_NAMESPACE}.anomaly_score` as const; -export const ALERT_ANOMALY_IS_INTERIM = `${ML_ALERT_NAMESPACE}.is_interim` as const; -export const ALERT_ANOMALY_TIMESTAMP = `${ML_ALERT_NAMESPACE}.anomaly_timestamp` as const; - -export const ALERT_TOP_RECORDS = `${ML_ALERT_NAMESPACE}.top_records` as const; -export const ALERT_TOP_INFLUENCERS = `${ML_ALERT_NAMESPACE}.top_influencers` as const; - export const ANOMALY_DETECTION_AAD_CONFIG: IRuleTypeAlerts = { context: ANOMALY_DETECTION_AAD_INDEX_NAME, mappings: { diff --git a/x-pack/plugins/ml/server/lib/capabilities/capabilities_switcher.ts b/x-pack/plugins/ml/server/lib/capabilities/capabilities_switcher.ts index ba08ab5066701b..e55f2dfc46d831 100644 --- a/x-pack/plugins/ml/server/lib/capabilities/capabilities_switcher.ts +++ b/x-pack/plugins/ml/server/lib/capabilities/capabilities_switcher.ts @@ -23,7 +23,9 @@ export const setupCapabilitiesSwitcher = ( enabledFeatures: MlFeatures, logger: Logger ) => { - coreSetup.capabilities.registerSwitcher(getSwitcher(license$, logger, enabledFeatures)); + coreSetup.capabilities.registerSwitcher(getSwitcher(license$, logger, enabledFeatures), { + capabilityPath: 'ml.*', + }); }; function getSwitcher( diff --git a/x-pack/plugins/ml/server/plugin.ts b/x-pack/plugins/ml/server/plugin.ts index d148fc8f148d9b..c268cd33832113 100644 --- a/x-pack/plugins/ml/server/plugin.ts +++ b/x-pack/plugins/ml/server/plugin.ts @@ -195,7 +195,9 @@ export class MlServerPlugin if (this.capabilities === null) { return null; } - const capabilities = await this.capabilities.resolveCapabilities(request); + const capabilities = await this.capabilities.resolveCapabilities(request, { + capabilityPath: 'ml.*', + }); return capabilities.ml as MlCapabilities; }; diff --git a/x-pack/plugins/ml/tsconfig.json b/x-pack/plugins/ml/tsconfig.json index 4a0077770808ac..cca832eee04237 100644 --- a/x-pack/plugins/ml/tsconfig.json +++ b/x-pack/plugins/ml/tsconfig.json @@ -108,5 +108,7 @@ "@kbn/data-view-editor-plugin", "@kbn/rule-data-utils", "@kbn/alerts-as-data-utils", + "@kbn/rule-registry-plugin", + "@kbn/securitysolution-ecs", ], } diff --git a/x-pack/plugins/monitoring/kibana.jsonc b/x-pack/plugins/monitoring/kibana.jsonc index 8da632c4b7d6ff..4992da373a73d3 100644 --- a/x-pack/plugins/monitoring/kibana.jsonc +++ b/x-pack/plugins/monitoring/kibana.jsonc @@ -1,7 +1,7 @@ { "type": "plugin", "id": "@kbn/monitoring-plugin", - "owner": "@elastic/infra-monitoring-ui", + "owner": "@elastic/obs-ux-infra_services-team", "plugin": { "id": "monitoring", "server": true, diff --git a/x-pack/plugins/monitoring_collection/kibana.jsonc b/x-pack/plugins/monitoring_collection/kibana.jsonc index 1c84d9ee4f84c1..246dcce086a074 100644 --- a/x-pack/plugins/monitoring_collection/kibana.jsonc +++ b/x-pack/plugins/monitoring_collection/kibana.jsonc @@ -1,7 +1,7 @@ { "type": "plugin", "id": "@kbn/monitoring-collection-plugin", - "owner": "@elastic/infra-monitoring-ui", + "owner": "@elastic/obs-ux-infra_services-team", "plugin": { "id": "monitoringCollection", "server": true, diff --git a/x-pack/plugins/observability/kibana.jsonc b/x-pack/plugins/observability/kibana.jsonc index 86db25972fb1b0..c03e0b499d4248 100644 --- a/x-pack/plugins/observability/kibana.jsonc +++ b/x-pack/plugins/observability/kibana.jsonc @@ -1,7 +1,7 @@ { "type": "plugin", "id": "@kbn/observability-plugin", - "owner": "@elastic/actionable-observability", + "owner": "@elastic/obs-ux-management-team", "plugin": { "id": "observability", "server": true, diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/custom_equation/custom_equation_editor.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/custom_equation/custom_equation_editor.tsx index c835f636eaa2d8..d0871dbfb75b24 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/components/custom_equation/custom_equation_editor.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/custom_equation/custom_equation_editor.tsx @@ -167,7 +167,6 @@ export function CustomEquationEditor({ { defaultMessage: 'Equation and threshold' } )} error={[errors.equation]} - isInvalid={errors.equation != null} > <> @@ -182,6 +181,7 @@ export function CustomEquationEditor({ onClick={() => { setCustomEqPopoverOpen(true); }} + isInvalid={errors.equation != null} /> diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/custom_equation/metric_row_with_agg.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/custom_equation/metric_row_with_agg.tsx index 80a379134806cf..891c0ceaabbb54 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/components/custom_equation/metric_row_with_agg.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/custom_equation/metric_row_with_agg.tsx @@ -38,6 +38,11 @@ interface MetricRowWithAggProps extends MetricRowBaseProps { fields: NormalizedFields; } +const DEFAULT_COUNT_FILTER_TITLE = i18n.translate( + 'xpack.observability.customThreshold.rule.alertFlyout.customEquationEditor.defaultCountFilterTitle', + { defaultMessage: 'all documents' } +); + export function MetricRowWithAgg({ name, aggType = Aggregators.COUNT, @@ -121,17 +126,19 @@ export function MetricRowWithAgg({ 'xpack.observability.customThreshold.rule.alertFlyout.customEquationEditor.aggregationLabel', { defaultMessage: 'Aggregation {name}', values: { name } } )} - isInvalid={aggType !== Aggregators.COUNT && !field} > { setAggTypePopoverOpen(true); }} + isInvalid={aggType !== Aggregators.COUNT && !field} /> } @@ -160,7 +167,6 @@ export function MetricRowWithAgg({ 'xpack.observability.customThreshold.rule.alertFlyout.customEquationEditor.aggregationType', { defaultMessage: 'Aggregation type' } )} - isInvalid={isAggInvalid} > @@ -201,7 +208,6 @@ export function MetricRowWithAgg({ 'xpack.observability.customThreshold.rule.alertFlyout.customEquationEditor.fieldLabel', { defaultMessage: 'Field name' } )} - isInvalid={isFieldInvalid} > +{ public readonly type = SLO_EMBEDDABLE; constructor( @@ -42,6 +45,15 @@ export class SloOverviewEmbeddableFactoryDefinition implements EmbeddableFactory } } + public getPanelPlacementSettings: IProvidesPanelPlacementSettings< + SloEmbeddableInput, + unknown + >['getPanelPlacementSettings'] = () => { + const width = 8; + const height = 7; + return { width, height, strategy: 'placeAtTop' }; + }; + public async create(initialInput: SloEmbeddableInput, parent?: IContainer) { try { const [{ uiSettings, application, http, i18n: i18nService }] = await this.getStartServices(); diff --git a/x-pack/plugins/observability/public/hooks/slo/use_fetch_active_alerts.ts b/x-pack/plugins/observability/public/hooks/slo/use_fetch_active_alerts.ts index 31cf30ca03a1dd..2d234b57ab8edb 100644 --- a/x-pack/plugins/observability/public/hooks/slo/use_fetch_active_alerts.ts +++ b/x-pack/plugins/observability/public/hooks/slo/use_fetch_active_alerts.ts @@ -49,6 +49,7 @@ type SloIdAndInstanceId = [string, string]; interface Params { sloIdsAndInstanceIds: SloIdAndInstanceId[]; + shouldRefetch?: boolean; } export interface UseFetchActiveAlerts { @@ -70,9 +71,13 @@ interface FindApiResponse { }; } +const LONG_REFETCH_INTERVAL = 1000 * 60; // 1 minute const EMPTY_ACTIVE_ALERTS_MAP = new ActiveAlerts(); -export function useFetchActiveAlerts({ sloIdsAndInstanceIds = [] }: Params): UseFetchActiveAlerts { +export function useFetchActiveAlerts({ + sloIdsAndInstanceIds = [], + shouldRefetch = false, +}: Params): UseFetchActiveAlerts { const { http } = useKibana().services; const { isInitialLoading, isLoading, isError, isSuccess, isRefetching, data } = useQuery({ @@ -136,6 +141,7 @@ export function useFetchActiveAlerts({ sloIdsAndInstanceIds = [] }: Params): Use } }, refetchOnWindowFocus: false, + refetchInterval: shouldRefetch ? LONG_REFETCH_INTERVAL : undefined, enabled: Boolean(sloIdsAndInstanceIds.length), }); diff --git a/x-pack/plugins/observability/public/pages/slo_details/components/slo_details.tsx b/x-pack/plugins/observability/public/pages/slo_details/components/slo_details.tsx index d70a4cbbfcbe32..e05c9c7403dd69 100644 --- a/x-pack/plugins/observability/public/pages/slo_details/components/slo_details.tsx +++ b/x-pack/plugins/observability/public/pages/slo_details/components/slo_details.tsx @@ -42,6 +42,7 @@ export function SloDetails({ slo, isAutoRefreshing }: Props) { const { search } = useLocation(); const { data: activeAlerts } = useFetchActiveAlerts({ sloIdsAndInstanceIds: [[slo.id, slo.instanceId ?? ALL_VALUE]], + shouldRefetch: isAutoRefreshing, }); const { isLoading: historicalSummaryLoading, data: historicalSummaries = [] } = useFetchHistoricalSummary({ diff --git a/x-pack/plugins/observability/public/pages/slo_edit/components/custom_metric/metric_indicator.tsx b/x-pack/plugins/observability/public/pages/slo_edit/components/custom_metric/metric_indicator.tsx index ec413f46df5ddc..262e6e6d2249a0 100644 --- a/x-pack/plugins/observability/public/pages/slo_edit/components/custom_metric/metric_indicator.tsx +++ b/x-pack/plugins/observability/public/pages/slo_edit/components/custom_metric/metric_indicator.tsx @@ -22,6 +22,10 @@ import { first, range, xor } from 'lodash'; import React, { useEffect, useState } from 'react'; import { Controller, useFieldArray, useFormContext } from 'react-hook-form'; import { Field } from '../../../../hooks/slo/use_fetch_index_pattern_fields'; +import { + aggValueToLabel, + CUSTOM_METRIC_AGGREGATION_OPTIONS, +} from '../../helpers/aggregation_options'; import { createOptionsFromFields, Option } from '../../helpers/create_options'; import { CreateSLOForm } from '../../types'; import { QueryBuilder } from '../common/query_builder'; @@ -62,7 +66,8 @@ const metricTooltip = ( content={i18n.translate( 'xpack.observability.slo.sloEdit.sliType.customMetric.totalMetric.tooltip', { - defaultMessage: 'This data from this field will be aggregated with the "sum" aggregation.', + defaultMessage: + 'This data from this field will be aggregated with the "sum" aggregation or document count.', } )} position="top" @@ -89,6 +94,7 @@ const equationTooltip = ( export function MetricIndicator({ type, metricFields, isLoadingIndex }: MetricIndicatorProps) { const { control, watch, setValue, register, getFieldState } = useFormContext(); const [options, setOptions] = useState(createOptionsFromFields(metricFields)); + const [aggregationOptions, setAggregationOptions] = useState(CUSTOM_METRIC_AGGREGATION_OPTIONS); useEffect(() => { setOptions(createOptionsFromFields(metricFields)); @@ -131,20 +137,25 @@ export function MetricIndicator({ type, metricFields, isLoadingIndex }: MetricIn {fields?.map((metric, index) => ( - - {metricLabel} {metric.name} {metricTooltip} + {i18n.translate( + 'xpack.observability.slo.sloEdit.customMetric.aggregationLabel', + { defaultMessage: 'Aggregation' } + )}{' '} + {metric.name} } > ( @@ -153,17 +164,13 @@ export function MetricIndicator({ type, metricFields, isLoadingIndex }: MetricIn async fullWidth singleSelection={{ asPlainText: true }} - prepend={i18n.translate( - 'xpack.observability.slo.sloEdit.sliType.customMetric.sumLabel', - { defaultMessage: 'Sum of' } - )} placeholder={i18n.translate( - 'xpack.observability.slo.sloEdit.sliType.customMetric.metricField.placeholder', - { defaultMessage: 'Select a metric field' } + 'xpack.observability.slo.sloEdit.sliType.customMetric.aggregation.placeholder', + { defaultMessage: 'Select an aggregation' } )} aria-label={i18n.translate( - 'xpack.observability.slo.sloEdit.sliType.customMetric.metricField.placeholder', - { defaultMessage: 'Select a metric field' } + 'xpack.observability.slo.sloEdit.sliType.customMetric.aggregation.placeholder', + { defaultMessage: 'Select an aggregation' } )} isClearable isInvalid={fieldState.invalid} @@ -178,40 +185,112 @@ export function MetricIndicator({ type, metricFields, isLoadingIndex }: MetricIn selectedOptions={ !!indexPattern && !!field.value && - metricFields.some((metricField) => metricField.name === field.value) + CUSTOM_METRIC_AGGREGATION_OPTIONS.some((agg) => agg.value === field.value) ? [ { value: field.value, - label: field.value, + label: aggValueToLabel(field.value), }, ] : [] } onSearchChange={(searchValue: string) => { - setOptions( - createOptionsFromFields(metricFields, ({ value }) => + setAggregationOptions( + CUSTOM_METRIC_AGGREGATION_OPTIONS.filter(({ value }) => value.includes(searchValue) ) ); }} - options={options} + options={aggregationOptions} /> )} /> + {watch(`indicator.params.${type}.metrics.${index}.aggregation`) !== 'doc_count' && ( + + + {metricLabel} {metric.name} {metricTooltip} + + } + > + ( + { + if (selected.length) { + return field.onChange(selected[0].value); + } + field.onChange(''); + }} + selectedOptions={ + !!indexPattern && + !!field.value && + metricFields.some((metricField) => metricField.name === field.value) + ? [ + { + value: field.value, + label: field.value, + }, + ] + : [] + } + onSearchChange={(searchValue: string) => { + setOptions( + createOptionsFromFields(metricFields, ({ value }) => + value.includes(searchValue) + ) + ); + }} + options={options} + /> + )} + /> + + + )} agg.value === agg.value) + AGGREGATION_OPTIONS.some((agg) => agg.value === field.value) ? [ { value: field.value, diff --git a/x-pack/plugins/observability/public/pages/slo_edit/helpers/aggregation_options.ts b/x-pack/plugins/observability/public/pages/slo_edit/helpers/aggregation_options.ts index 4a3a5fb9cf28ac..ab27dcd68efe58 100644 --- a/x-pack/plugins/observability/public/pages/slo_edit/helpers/aggregation_options.ts +++ b/x-pack/plugins/observability/public/pages/slo_edit/helpers/aggregation_options.ts @@ -75,6 +75,10 @@ export const AGGREGATION_OPTIONS = [ }, ]; +export const CUSTOM_METRIC_AGGREGATION_OPTIONS = AGGREGATION_OPTIONS.filter((option) => + ['doc_count', 'sum'].includes(option.value) +); + export function aggValueToLabel(value: string) { const aggregation = AGGREGATION_OPTIONS.find((agg) => agg.value === value); if (aggregation) { diff --git a/x-pack/plugins/observability/public/pages/slo_edit/hooks/use_section_form_validation.ts b/x-pack/plugins/observability/public/pages/slo_edit/hooks/use_section_form_validation.ts index 6fede4552d6f89..e8cabe602e7ccc 100644 --- a/x-pack/plugins/observability/public/pages/slo_edit/hooks/use_section_form_validation.ts +++ b/x-pack/plugins/observability/public/pages/slo_edit/hooks/use_section_form_validation.ts @@ -6,6 +6,8 @@ */ import { + metricCustomBasicMetric, + metricCustomDocCountMetric, MetricCustomIndicator, timesliceMetricBasicMetricWithField, TimesliceMetricIndicator, @@ -31,7 +33,16 @@ export function useSectionFormValidation({ getFieldState, getValues, formState, const data = getValues('indicator.params.good') as MetricCustomIndicator['params']['good']; const isEquationValid = !getFieldState('indicator.params.good.equation').invalid; const areMetricsValid = - isObject(data) && (data.metrics ?? []).every((metric) => Boolean(metric.field)); + isObject(data) && + (data.metrics ?? []).every((metric) => { + if (metricCustomDocCountMetric.is(metric)) { + return true; + } + if (metricCustomBasicMetric.is(metric) && metric.field != null) { + return true; + } + return false; + }); return isEquationValid && areMetricsValid; }; @@ -41,7 +52,16 @@ export function useSectionFormValidation({ getFieldState, getValues, formState, ) as MetricCustomIndicator['params']['total']; const isEquationValid = !getFieldState('indicator.params.total.equation').invalid; const areMetricsValid = - isObject(data) && (data.metrics ?? []).every((metric) => Boolean(metric.field)); + isObject(data) && + (data.metrics ?? []).every((metric) => { + if (metricCustomDocCountMetric.is(metric)) { + return true; + } + if (metricCustomBasicMetric.is(metric) && metric.field != null) { + return true; + } + return false; + }); return isEquationValid && areMetricsValid; }; 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 21972da276691e..8d4d45a08d7090 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 @@ -5,8 +5,7 @@ * 2.0. */ -import { fireEvent, waitFor } from '@testing-library/dom'; -import { cleanup } from '@testing-library/react'; +import { fireEvent, waitFor, cleanup } from '@testing-library/react'; import { createBrowserHistory } from 'history'; import React from 'react'; import Router from 'react-router-dom'; diff --git a/x-pack/plugins/observability/server/index.ts b/x-pack/plugins/observability/server/index.ts index 96231115e3fa2a..2153d00abdbc68 100644 --- a/x-pack/plugins/observability/server/index.ts +++ b/x-pack/plugins/observability/server/index.ts @@ -48,7 +48,7 @@ const configSchema = schema.object({ }), thresholdRule: schema.object({ enabled: offeringBasedSchema({ - serverless: schema.boolean({ defaultValue: false }), + serverless: schema.boolean({ defaultValue: true }), traditional: schema.boolean({ defaultValue: true }), }), }), diff --git a/x-pack/plugins/observability/server/plugin.ts b/x-pack/plugins/observability/server/plugin.ts index 4e2c7a57ead137..17476d335f48f2 100644 --- a/x-pack/plugins/observability/server/plugin.ts +++ b/x-pack/plugins/observability/server/plugin.ts @@ -29,6 +29,7 @@ import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server'; import { ApmRuleType, ES_QUERY_ID, + METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID, OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, } from '@kbn/rule-data-utils'; import { ObservabilityConfig } from '.'; @@ -81,6 +82,7 @@ const o11yRuleTypes = [ SLO_BURN_RATE_RULE_TYPE_ID, OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, ES_QUERY_ID, + METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID, ...Object.values(ApmRuleType), ]; diff --git a/x-pack/plugins/observability/server/services/slo/aggregations/__snapshots__/get_custom_metric_indicator_aggregation.test.ts.snap b/x-pack/plugins/observability/server/services/slo/aggregations/__snapshots__/get_custom_metric_indicator_aggregation.test.ts.snap index f5558a33f1981b..6bf8f0ad13e560 100644 --- a/x-pack/plugins/observability/server/services/slo/aggregations/__snapshots__/get_custom_metric_indicator_aggregation.test.ts.snap +++ b/x-pack/plugins/observability/server/services/slo/aggregations/__snapshots__/get_custom_metric_indicator_aggregation.test.ts.snap @@ -4,7 +4,7 @@ exports[`GetHistogramIndicatorAggregation should generate a aggregation for good Object { "_good_A": Object { "aggs": Object { - "sum": Object { + "metric": Object { "sum": Object { "field": "total", }, @@ -16,7 +16,7 @@ Object { }, "_good_B": Object { "aggs": Object { - "sum": Object { + "metric": Object { "sum": Object { "field": "processed", }, @@ -29,8 +29,8 @@ Object { "goodEvents": Object { "bucket_script": Object { "buckets_path": Object { - "A": "_good_A>sum", - "B": "_good_B>sum", + "A": "_good_A>metric", + "B": "_good_B>metric", }, "script": Object { "lang": "painless", @@ -45,7 +45,7 @@ exports[`GetHistogramIndicatorAggregation should generate a aggregation for tota Object { "_total_A": Object { "aggs": Object { - "sum": Object { + "metric": Object { "sum": Object { "field": "total", }, @@ -58,7 +58,7 @@ Object { "totalEvents": Object { "bucket_script": Object { "buckets_path": Object { - "A": "_total_A>sum", + "A": "_total_A>metric", }, "script": Object { "lang": "painless", diff --git a/x-pack/plugins/observability/server/services/slo/aggregations/get_custom_metric_indicator_aggregation.ts b/x-pack/plugins/observability/server/services/slo/aggregations/get_custom_metric_indicator_aggregation.ts index 73bbb91b1041f2..6c3439eb9a146b 100644 --- a/x-pack/plugins/observability/server/services/slo/aggregations/get_custom_metric_indicator_aggregation.ts +++ b/x-pack/plugins/observability/server/services/slo/aggregations/get_custom_metric_indicator_aggregation.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { MetricCustomIndicator } from '@kbn/slo-schema'; +import { metricCustomDocCountMetric, MetricCustomIndicator } from '@kbn/slo-schema'; import { getElastichsearchQueryOrThrow } from '../transform_generators'; type MetricCustomMetricDef = @@ -20,12 +20,22 @@ export class GetCustomMetricIndicatorAggregation { const filter = metric.filter ? getElastichsearchQueryOrThrow(metric.filter) : { match_all: {} }; + + if (metricCustomDocCountMetric.is(metric)) { + return { + ...acc, + [`_${type}_${metric.name}`]: { + filter, + }, + }; + } + return { ...acc, [`_${type}_${metric.name}`]: { filter, aggs: { - sum: { + metric: { [metric.aggregation]: { field: metric.field }, }, }, @@ -42,10 +52,11 @@ export class GetCustomMetricIndicatorAggregation { } private buildMetricEquation(type: 'good' | 'total', metricDef: MetricCustomMetricDef) { - const bucketsPath = metricDef.metrics.reduce( - (acc, metric) => ({ ...acc, [metric.name]: `_${type}_${metric.name}>sum` }), - {} - ); + const bucketsPath = metricDef.metrics.reduce((acc, metric) => { + const path = metricCustomDocCountMetric.is(metric) ? '_count' : 'metric'; + return { ...acc, [metric.name]: `_${type}_${metric.name}>${path}` }; + }, {}); + return { bucket_script: { buckets_path: bucketsPath, diff --git a/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/metric_custom.test.ts.snap b/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/metric_custom.test.ts.snap index 7c1765953d1540..55ed414bd2ec7e 100644 --- a/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/metric_custom.test.ts.snap +++ b/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/metric_custom.test.ts.snap @@ -1,10 +1,38 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`Metric Custom Transform Generator aggregates using doc_count for the denominator equation with filter 1`] = ` +Object { + "bucket_script": Object { + "buckets_path": Object { + "A": "_total_A>_count", + }, + "script": Object { + "lang": "painless", + "source": "params.A / 100", + }, + }, +} +`; + +exports[`Metric Custom Transform Generator aggregates using doc_count the numerator equation with filter 1`] = ` +Object { + "bucket_script": Object { + "buckets_path": Object { + "A": "_good_A>_count", + }, + "script": Object { + "lang": "painless", + "source": "params.A * 100", + }, + }, +} +`; + exports[`Metric Custom Transform Generator aggregates using the denominator equation 1`] = ` Object { "bucket_script": Object { "buckets_path": Object { - "A": "_total_A>sum", + "A": "_total_A>metric", }, "script": Object { "lang": "painless", @@ -18,7 +46,7 @@ exports[`Metric Custom Transform Generator aggregates using the denominator equa Object { "bucket_script": Object { "buckets_path": Object { - "A": "_total_A>sum", + "A": "_total_A>metric", }, "script": Object { "lang": "painless", @@ -32,7 +60,7 @@ exports[`Metric Custom Transform Generator aggregates using the numerator equati Object { "bucket_script": Object { "buckets_path": Object { - "A": "_good_A>sum", + "A": "_good_A>metric", }, "script": Object { "lang": "painless", @@ -46,7 +74,7 @@ exports[`Metric Custom Transform Generator aggregates using the numerator equati Object { "bucket_script": Object { "buckets_path": Object { - "A": "_good_A>sum", + "A": "_good_A>metric", }, "script": Object { "lang": "painless", @@ -101,7 +129,7 @@ Object { "aggregations": Object { "_good_A": Object { "aggs": Object { - "sum": Object { + "metric": Object { "sum": Object { "field": "total", }, @@ -113,7 +141,7 @@ Object { }, "_good_B": Object { "aggs": Object { - "sum": Object { + "metric": Object { "sum": Object { "field": "processed", }, @@ -125,7 +153,7 @@ Object { }, "_total_A": Object { "aggs": Object { - "sum": Object { + "metric": Object { "sum": Object { "field": "total", }, @@ -138,7 +166,7 @@ Object { "slo.denominator": Object { "bucket_script": Object { "buckets_path": Object { - "A": "_total_A>sum", + "A": "_total_A>metric", }, "script": Object { "lang": "painless", @@ -158,8 +186,8 @@ Object { "slo.numerator": Object { "bucket_script": Object { "buckets_path": Object { - "A": "_good_A>sum", - "B": "_good_B>sum", + "A": "_good_A>metric", + "B": "_good_B>metric", }, "script": Object { "lang": "painless", @@ -384,7 +412,7 @@ Object { "aggregations": Object { "_good_A": Object { "aggs": Object { - "sum": Object { + "metric": Object { "sum": Object { "field": "total", }, @@ -396,7 +424,7 @@ Object { }, "_good_B": Object { "aggs": Object { - "sum": Object { + "metric": Object { "sum": Object { "field": "processed", }, @@ -408,7 +436,7 @@ Object { }, "_total_A": Object { "aggs": Object { - "sum": Object { + "metric": Object { "sum": Object { "field": "total", }, @@ -421,7 +449,7 @@ Object { "slo.denominator": Object { "bucket_script": Object { "buckets_path": Object { - "A": "_total_A>sum", + "A": "_total_A>metric", }, "script": Object { "lang": "painless", @@ -432,8 +460,8 @@ Object { "slo.numerator": Object { "bucket_script": Object { "buckets_path": Object { - "A": "_good_A>sum", - "B": "_good_B>sum", + "A": "_good_A>metric", + "B": "_good_B>metric", }, "script": Object { "lang": "painless", @@ -629,3 +657,17 @@ Object { "transform_id": Any, } `; + +exports[`Metric Custom Transform Generator support the same field used twice in the equation 1`] = ` +Object { + "bucket_script": Object { + "buckets_path": Object { + "A": "_good_A>metric", + }, + "script": Object { + "lang": "painless", + "source": "params.A + params.A * 100", + }, + }, +} +`; diff --git a/x-pack/plugins/observability/server/services/slo/transform_generators/metric_custom.test.ts b/x-pack/plugins/observability/server/services/slo/transform_generators/metric_custom.test.ts index beea8164b1c995..69685bad0c09e3 100644 --- a/x-pack/plugins/observability/server/services/slo/transform_generators/metric_custom.test.ts +++ b/x-pack/plugins/observability/server/services/slo/transform_generators/metric_custom.test.ts @@ -142,6 +142,20 @@ describe('Metric Custom Transform Generator', () => { expect(transform.pivot!.aggregations!['slo.numerator']).toMatchSnapshot(); }); + it('support the same field used twice in the equation', async () => { + const anSLO = createSLO({ + indicator: createMetricCustomIndicator({ + good: { + metrics: [{ name: 'A', aggregation: 'sum', field: 'good' }], + equation: 'A + A * 100', + }, + }), + }); + const transform = generator.getTransformParams(anSLO); + + expect(transform.pivot!.aggregations!['slo.numerator']).toMatchSnapshot(); + }); + it('aggregates using the numerator equation with filter', async () => { const anSLO = createSLO({ indicator: createMetricCustomIndicator({ @@ -158,6 +172,20 @@ describe('Metric Custom Transform Generator', () => { expect(transform.pivot!.aggregations!['slo.numerator']).toMatchSnapshot(); }); + it('aggregates using doc_count the numerator equation with filter', async () => { + const anSLO = createSLO({ + indicator: createMetricCustomIndicator({ + good: { + metrics: [{ name: 'A', aggregation: 'doc_count', filter: 'outcome: "success" ' }], + equation: 'A * 100', + }, + }), + }); + const transform = generator.getTransformParams(anSLO); + + expect(transform.pivot!.aggregations!['slo.numerator']).toMatchSnapshot(); + }); + it('aggregates using the denominator equation', async () => { const anSLO = createSLO({ indicator: createMetricCustomIndicator({ @@ -185,4 +213,18 @@ describe('Metric Custom Transform Generator', () => { expect(transform.pivot!.aggregations!['slo.denominator']).toMatchSnapshot(); }); + + it('aggregates using doc_count for the denominator equation with filter', async () => { + const anSLO = createSLO({ + indicator: createMetricCustomIndicator({ + total: { + metrics: [{ name: 'A', aggregation: 'doc_count', filter: 'outcome: *' }], + equation: 'A / 100', + }, + }), + }); + const transform = generator.getTransformParams(anSLO); + + expect(transform.pivot!.aggregations!['slo.denominator']).toMatchSnapshot(); + }); }); diff --git a/x-pack/plugins/observability/tsconfig.json b/x-pack/plugins/observability/tsconfig.json index d0d4114fe46126..4e2f8ef70482c5 100644 --- a/x-pack/plugins/observability/tsconfig.json +++ b/x-pack/plugins/observability/tsconfig.json @@ -93,7 +93,8 @@ "@kbn/react-kibana-context-theme", "@kbn/shared-ux-link-redirect-app", "@kbn/core-chrome-browser", - "@kbn/serverless" + "@kbn/serverless", + "@kbn/dashboard-plugin" ], "exclude": [ "target/**/*" diff --git a/x-pack/plugins/observability_ai_assistant/kibana.jsonc b/x-pack/plugins/observability_ai_assistant/kibana.jsonc index 3af934a10fcfac..291c7e658de187 100644 --- a/x-pack/plugins/observability_ai_assistant/kibana.jsonc +++ b/x-pack/plugins/observability_ai_assistant/kibana.jsonc @@ -1,7 +1,7 @@ { "type": "plugin", "id": "@kbn/observability-ai-assistant-plugin", - "owner": "@elastic/obs-ai-assistant", + "owner": "@elastic/obs-knowledge-team", "plugin": { "id": "observabilityAIAssistant", "server": true, diff --git a/x-pack/plugins/observability_log_explorer/kibana.jsonc b/x-pack/plugins/observability_log_explorer/kibana.jsonc index 7ac940de86dd49..72d03b82d3386b 100644 --- a/x-pack/plugins/observability_log_explorer/kibana.jsonc +++ b/x-pack/plugins/observability_log_explorer/kibana.jsonc @@ -1,7 +1,7 @@ { "type": "plugin", "id": "@kbn/observability-log-explorer-plugin", - "owner": "@elastic/infra-monitoring-ui", + "owner": "@elastic/obs-ux-logs-team", "description": "This plugin exposes and registers observability log consumption features.", "plugin": { "id": "observabilityLogExplorer", @@ -15,6 +15,7 @@ "data", "discover", "logExplorer", + "logsShared", "observabilityShared", "share", "kibanaUtils", diff --git a/x-pack/plugins/observability_log_explorer/public/log_explorer_customizations/flyout_content.tsx b/x-pack/plugins/observability_log_explorer/public/log_explorer_customizations/flyout_content.tsx new file mode 100644 index 00000000000000..53d34a71a72371 --- /dev/null +++ b/x-pack/plugins/observability_log_explorer/public/log_explorer_customizations/flyout_content.tsx @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { EuiFlexItem } from '@elastic/eui'; +import { + LogExplorerCustomizations, + LogExplorerFlyoutContentProps, +} from '@kbn/log-explorer-plugin/public'; +import type { LogAIAssistantDocument } from '@kbn/logs-shared-plugin/public'; +import React, { useMemo } from 'react'; +import { useKibanaContextForPlugin } from '../utils/use_kibana'; + +const ObservabilityLogAIAssistant = ({ doc }: LogExplorerFlyoutContentProps) => { + const { services } = useKibanaContextForPlugin(); + const { LogAIAssistant } = services.logsShared; + + const mappedDoc = useMemo(() => mapDocToAIAssistantFormat(doc), [doc]); + + return ; +}; + +export const renderFlyoutContent: Required['flyout']['renderContent'] = ( + renderPreviousContent, + props +) => { + return ( + <> + {renderPreviousContent()} + + + + + ); +}; + +/** + * Utils + */ +const mapDocToAIAssistantFormat = (doc: LogExplorerFlyoutContentProps['doc']) => { + if (!doc) return; + + return { + fields: Object.entries(doc.flattened).map(([field, value]) => ({ + field, + value, + })) as LogAIAssistantDocument['fields'], + }; +}; diff --git a/x-pack/plugins/observability_log_explorer/public/log_explorer_customizations/index.ts b/x-pack/plugins/observability_log_explorer/public/log_explorer_customizations/index.ts new file mode 100644 index 00000000000000..a86b47e92cf01b --- /dev/null +++ b/x-pack/plugins/observability_log_explorer/public/log_explorer_customizations/index.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { LogExplorerCustomizations } from '@kbn/log-explorer-plugin/public'; +import { renderFlyoutContent } from './flyout_content'; + +export const createLogExplorerCustomizations = (): LogExplorerCustomizations => ({ + flyout: { + renderContent: renderFlyoutContent, + }, +}); diff --git a/x-pack/plugins/observability_log_explorer/public/routes/main/main_route.tsx b/x-pack/plugins/observability_log_explorer/public/routes/main/main_route.tsx index aece8474f03903..e17f92a46c23b5 100644 --- a/x-pack/plugins/observability_log_explorer/public/routes/main/main_route.tsx +++ b/x-pack/plugins/observability_log_explorer/public/routes/main/main_route.tsx @@ -6,7 +6,7 @@ */ import { CoreStart } from '@kbn/core/public'; -import React, { useState } from 'react'; +import React, { useMemo, useState } from 'react'; import { BehaviorSubject } from 'rxjs'; import { LogExplorerTopNavMenu } from '../../components/log_explorer_top_nav_menu'; import { ObservabilityLogExplorerPageTemplate } from '../../components/page_template'; @@ -14,6 +14,7 @@ import { noBreadcrumbs, useBreadcrumbs } from '../../utils/breadcrumbs'; import { useKibanaContextForPlugin } from '../../utils/use_kibana'; import { ObservabilityLogExplorerAppMountParameters } from '../../types'; import { LazyOriginInterpreter } from '../../state_machines/origin_interpreter/src/lazy_component'; +import { createLogExplorerCustomizations } from '../../log_explorer_customizations'; export interface ObservablityLogExplorerMainRouteProps { appParams: ObservabilityLogExplorerAppMountParameters; core: CoreStart; @@ -31,6 +32,8 @@ export const ObservablityLogExplorerMainRoute = ({ const [state$] = useState(() => new BehaviorSubject({})); + const customizations = useMemo(() => createLogExplorerCustomizations(), []); + return ( <> - + ); diff --git a/x-pack/plugins/observability_log_explorer/public/types.ts b/x-pack/plugins/observability_log_explorer/public/types.ts index 82045faea76e8f..8b315ad206ce45 100644 --- a/x-pack/plugins/observability_log_explorer/public/types.ts +++ b/x-pack/plugins/observability_log_explorer/public/types.ts @@ -12,6 +12,7 @@ import { ObservabilitySharedPluginStart } from '@kbn/observability-shared-plugin import { ServerlessPluginStart } from '@kbn/serverless/public'; import { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/public'; import { AppMountParameters, ScopedHistory } from '@kbn/core/public'; +import { LogsSharedClientStartExports } from '@kbn/logs-shared-plugin/public'; import { ObservabilityLogExplorerLocators, ObservabilityLogExplorerLocationState, @@ -33,6 +34,7 @@ export interface ObservabilityLogExplorerStartDeps { data: DataPublicPluginStart; discover: DiscoverStart; logExplorer: LogExplorerPluginStart; + logsShared: LogsSharedClientStartExports; observabilityShared: ObservabilitySharedPluginStart; serverless?: ServerlessPluginStart; share: SharePluginStart; diff --git a/x-pack/plugins/observability_log_explorer/tsconfig.json b/x-pack/plugins/observability_log_explorer/tsconfig.json index 7266e097dae626..109b54b929ec7b 100644 --- a/x-pack/plugins/observability_log_explorer/tsconfig.json +++ b/x-pack/plugins/observability_log_explorer/tsconfig.json @@ -33,7 +33,8 @@ "@kbn/core-mount-utils-browser-internal", "@kbn/xstate-utils", "@kbn/shared-ux-utility", - "@kbn/ui-theme" + "@kbn/ui-theme", + "@kbn/logs-shared-plugin" ], "exclude": [ "target/**/*" diff --git a/x-pack/plugins/observability_onboarding/kibana.jsonc b/x-pack/plugins/observability_onboarding/kibana.jsonc index 5c1615c3a95baf..c2b89c8c9b4fa9 100644 --- a/x-pack/plugins/observability_onboarding/kibana.jsonc +++ b/x-pack/plugins/observability_onboarding/kibana.jsonc @@ -1,7 +1,7 @@ { "type": "plugin", "id": "@kbn/observability-onboarding-plugin", - "owner": "@elastic/apm-ui", + "owner": "@elastic/obs-ux-logs-team", "plugin": { "id": "observabilityOnboarding", "server": true, diff --git a/x-pack/plugins/osquery/cypress/e2e/all/add_integration.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/add_integration.cy.ts index 6eae4381cf34be..90875bebc8384d 100644 --- a/x-pack/plugins/osquery/cypress/e2e/all/add_integration.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/all/add_integration.cy.ts @@ -5,10 +5,12 @@ * 2.0. */ +import { LIVE_QUERY_EDITOR } from '../../screens/live_query'; import { ADD_PACK_HEADER_BUTTON, ADD_QUERY_BUTTON, formFieldInputSelector, + SAVED_QUERY_DROPDOWN_SELECT, TABLE_ROWS, } from '../../screens/packs'; import { @@ -178,11 +180,15 @@ describe('ALL - Add Integration', { tags: ['@ess', '@serverless'] }, () => { navigateTo('app/osquery/packs'); cy.getBySel(ADD_PACK_HEADER_BUTTON).click(); + cy.getBySel('globalLoadingIndicator').should('not.exist'); + cy.get(formFieldInputSelector('name')).type(`${packName}{downArrow}{enter}`); cy.getBySel('policyIdsComboBox').type(`${policyName} {downArrow}{enter}`); cy.getBySel(ADD_QUERY_BUTTON).click(); - cy.getBySel('savedQuerySelect').click().type('{downArrow}{enter}'); + cy.getBySel('globalLoadingIndicator').should('not.exist'); + cy.getBySel(LIVE_QUERY_EDITOR).should('exist'); + cy.getBySel(SAVED_QUERY_DROPDOWN_SELECT).click().type('{downArrow}{enter}'); cy.contains(/^Save$/).click(); cy.contains(/^Save pack$/).click(); cy.contains(`Successfully created "${packName}" pack`).click(); diff --git a/x-pack/plugins/osquery/cypress/e2e/all/alerts_cases.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/alerts_cases.cy.ts index 3c93bef865b96c..7f6f0a006e05b2 100644 --- a/x-pack/plugins/osquery/cypress/e2e/all/alerts_cases.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/all/alerts_cases.cy.ts @@ -71,6 +71,7 @@ describe('Alert Event Details - Cases', { tags: ['@ess', '@serverless'] }, () => cy.contains(/^\d+ agen(t|ts) selected/); cy.contains('Run a set of queries in a pack').click(); cy.get(OSQUERY_FLYOUT_BODY_EDITOR).should('not.exist'); + cy.getBySel('globalLoadingIndicator').should('not.exist'); cy.getBySel('select-live-pack').click().type(`${packName}{downArrow}{enter}`); submitQuery(); cy.get('[aria-label="Add to Case"]').first().click(); diff --git a/x-pack/plugins/osquery/cypress/e2e/all/alerts_response_actions_form.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/alerts_response_actions_form.cy.ts index 15fb98540e438a..cb4ce27a690315 100644 --- a/x-pack/plugins/osquery/cypress/e2e/all/alerts_response_actions_form.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/all/alerts_response_actions_form.cy.ts @@ -63,6 +63,7 @@ describe('Alert Event Details - Response Actions Form', { tags: ['@ess', '@serve cy.getBySel('globalLoadingIndicator').should('not.exist'); closeDateTabIfVisible(); cy.getBySel('edit-rule-actions-tab').click(); + cy.getBySel('globalLoadingIndicator').should('not.exist'); cy.contains('Response actions are run on each rule execution.'); cy.getBySel(OSQUERY_RESPONSE_ACTION_ADD_BUTTON).click(); cy.getBySel(RESPONSE_ACTIONS_ITEM_0).within(() => { diff --git a/x-pack/plugins/osquery/cypress/e2e/all/live_query.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/live_query.cy.ts index a5f9fdab66a999..3e84dc001eed53 100644 --- a/x-pack/plugins/osquery/cypress/e2e/all/live_query.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/all/live_query.cy.ts @@ -67,22 +67,22 @@ describe('ALL - Live Query', { tags: ['@ess', '@serverless'] }, () => { "where pos.remote_port !='0' {shift+enter}" + 'limit 1000;'; cy.contains('New live query').click(); - cy.get(LIVE_QUERY_EDITOR).invoke('height').and('be.gt', 99).and('be.lt', 110); - cy.get(LIVE_QUERY_EDITOR).click().invoke('val', multilineQuery); + cy.getBySel(LIVE_QUERY_EDITOR).invoke('height').and('be.gt', 99).and('be.lt', 110); + cy.getBySel(LIVE_QUERY_EDITOR).click().invoke('val', multilineQuery); inputQuery(multilineQuery); - cy.get(LIVE_QUERY_EDITOR).invoke('height').should('be.gt', 220).and('be.lt', 300); + cy.getBySel(LIVE_QUERY_EDITOR).invoke('height').should('be.gt', 220).and('be.lt', 300); selectAllAgents(); submitQuery(); cy.getBySel('osqueryResultsPanel'); // check if it get's bigger when we add more lines - cy.get(LIVE_QUERY_EDITOR).invoke('height').should('be.gt', 220).and('be.lt', 300); + cy.getBySel(LIVE_QUERY_EDITOR).invoke('height').should('be.gt', 220).and('be.lt', 300); inputQuery(multilineQuery); - cy.get(LIVE_QUERY_EDITOR).invoke('height').should('be.gt', 350).and('be.lt', 600); + cy.getBySel(LIVE_QUERY_EDITOR).invoke('height').should('be.gt', 350).and('be.lt', 600); inputQuery('{selectall}{backspace}{selectall}{backspace}'); // not sure if this is how it used to work when I implemented the functionality, but let's leave it like this for now - cy.get(LIVE_QUERY_EDITOR).invoke('height').should('be.gt', 200).and('be.lt', 400); + cy.getBySel(LIVE_QUERY_EDITOR).invoke('height').should('be.gt', 200).and('be.lt', 400); }); }); diff --git a/x-pack/plugins/osquery/cypress/e2e/all/live_query_packs.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/live_query_packs.cy.ts index 4269d0e3d79010..da598382bf0ee7 100644 --- a/x-pack/plugins/osquery/cypress/e2e/all/live_query_packs.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/all/live_query_packs.cy.ts @@ -67,7 +67,7 @@ describe('ALL - Live Query Packs', { tags: ['@ess', '@serverless'] }, () => { it('should run live pack', () => { cy.contains('New live query').click(); cy.contains('Run a set of queries in a pack.').click(); - cy.get(LIVE_QUERY_EDITOR).should('not.exist'); + cy.getBySel(LIVE_QUERY_EDITOR).should('not.exist'); cy.getBySel('select-live-pack').click().type(`${packName}{downArrow}{enter}`); cy.contains('This table contains 3 rows.'); cy.contains('system_memory_linux_elastic'); diff --git a/x-pack/plugins/osquery/cypress/e2e/all/live_query_run.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/live_query_run.cy.ts index 1200e3e6f610dc..035c228623fdae 100644 --- a/x-pack/plugins/osquery/cypress/e2e/all/live_query_run.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/all/live_query_run.cy.ts @@ -99,7 +99,7 @@ describe('ALL - Live Query run custom and saved', { tags: ['@ess', '@serverless' navigateTo('/app/osquery'); cy.get('[aria-label="Run query"]').first().should('be.visible').click(); - cy.get(LIVE_QUERY_EDITOR).contains('select * from users;'); + cy.getBySel(LIVE_QUERY_EDITOR).contains('select * from users;'); }); it('should open query details by clicking the details icon', () => { diff --git a/x-pack/plugins/osquery/cypress/e2e/all/packs_create_edit.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/packs_create_edit.cy.ts index 64cb28d93d22c3..32e2496f8ea06f 100644 --- a/x-pack/plugins/osquery/cypress/e2e/all/packs_create_edit.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/all/packs_create_edit.cy.ts @@ -33,7 +33,7 @@ import { interceptPackId, } from '../../tasks/integrations'; import { DEFAULT_POLICY } from '../../screens/fleet'; -import { getIdFormField } from '../../screens/live_query'; +import { getIdFormField, LIVE_QUERY_EDITOR } from '../../screens/live_query'; import { loadSavedQuery, cleanupSavedQuery, cleanupPack, loadPack } from '../../tasks/api_fixtures'; import { request } from '../../tasks/common'; import { ServerlessRoleName } from '../../support/roles'; @@ -254,6 +254,8 @@ describe('Packs - Create and Edit', { tags: ['@ess', '@serverless'] }, () => { cy.getBySel(ADD_QUERY_BUTTON).click(); cy.contains('Attach next query'); + cy.getBySel('globalLoadingIndicator').should('not.exist'); + cy.getBySel(LIVE_QUERY_EDITOR).should('exist'); cy.getBySel(SAVED_QUERY_DROPDOWN_SELECT).type(`${savedQueryName}{downArrow}{enter}`); cy.getBySel('osquery-interval-field').click().clear().type('5'); cy.getBySel(FLYOUT_SAVED_QUERY_SAVE_BUTTON).click(); @@ -367,6 +369,8 @@ describe('Packs - Create and Edit', { tags: ['@ess', '@serverless'] }, () => { cy.getBySel(ADD_QUERY_BUTTON).click(); cy.contains('Attach next query'); + cy.getBySel('globalLoadingIndicator').should('not.exist'); + cy.getBySel(LIVE_QUERY_EDITOR).should('exist'); cy.contains('ID must be unique').should('not.exist'); cy.getBySel(SAVED_QUERY_DROPDOWN_SELECT).type(`${savedQueryName}{downArrow}{enter}`); cy.getBySel(FLYOUT_SAVED_QUERY_SAVE_BUTTON).click(); @@ -671,6 +675,8 @@ describe('Packs - Create and Edit', { tags: ['@ess', '@serverless'] }, () => { cy.getBySel(ADD_QUERY_BUTTON).click(); + cy.getBySel('globalLoadingIndicator').should('not.exist'); + cy.getBySel(LIVE_QUERY_EDITOR).should('exist'); cy.getBySel(SAVED_QUERY_DROPDOWN_SELECT).type( `${multipleMappingsSavedQueryName} {downArrow} {enter}` ); diff --git a/x-pack/plugins/osquery/cypress/e2e/all/packs_integration.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/packs_integration.cy.ts index 73dc45837e2b37..30b8a1641fb946 100644 --- a/x-pack/plugins/osquery/cypress/e2e/all/packs_integration.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/all/packs_integration.cy.ts @@ -162,7 +162,7 @@ describe('ALL - Packs', { tags: ['@ess', '@serverless'] }, () => { navigateTo('/app/osquery/live_queries'); cy.contains('New live query').click(); cy.contains('Run a set of queries in a pack.').click(); - cy.get(LIVE_QUERY_EDITOR).should('not.exist'); + cy.getBySel(LIVE_QUERY_EDITOR).should('not.exist'); cy.getBySel('select-live-pack').click().type('osquery-monitoring{downArrow}{enter}'); selectAllAgents(); submitQuery(); diff --git a/x-pack/plugins/osquery/cypress/e2e/all/saved_queries.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/saved_queries.cy.ts index 1319d4e173ec4a..6638567a926256 100644 --- a/x-pack/plugins/osquery/cypress/e2e/all/saved_queries.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/all/saved_queries.cy.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { LIVE_QUERY_EDITOR } from '../../screens/live_query'; import { ADD_QUERY_BUTTON, customActionEditSavedQuerySelector, @@ -142,7 +143,8 @@ describe('ALL - Saved queries', { tags: ['@ess', '@serverless'] }, () => { cy.getBySel(ADD_QUERY_BUTTON).click(); cy.contains('Attach next query'); - + cy.getBySel('globalLoadingIndicator').should('not.exist'); + cy.getBySel(LIVE_QUERY_EDITOR).should('exist'); cy.getBySel(SAVED_QUERY_DROPDOWN_SELECT).click().type('users_elastic{downArrow} {enter}'); inputQuery('where name=1'); cy.getBySel('resultsTypeField').click(); diff --git a/x-pack/plugins/osquery/cypress/e2e/roles/t1_and_t2_analyst.cy.ts b/x-pack/plugins/osquery/cypress/e2e/roles/t1_and_t2_analyst.cy.ts index 470c8445304521..d512f5d5df5bb8 100644 --- a/x-pack/plugins/osquery/cypress/e2e/roles/t1_and_t2_analyst.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/roles/t1_and_t2_analyst.cy.ts @@ -113,7 +113,7 @@ describe(`T1 and T2 analysts`, { tags: ['@ess', '@serverless'] }, () => { cy.contains('New live query').click(); selectAllAgents(); - cy.get(LIVE_QUERY_EDITOR).should('not.exist'); + cy.getBySel(LIVE_QUERY_EDITOR).should('not.exist'); submitQuery(); cy.contains('Query is a required field'); }); diff --git a/x-pack/plugins/osquery/cypress/screens/live_query.ts b/x-pack/plugins/osquery/cypress/screens/live_query.ts index 9dc543072fe074..04c56fbce44c7b 100644 --- a/x-pack/plugins/osquery/cypress/screens/live_query.ts +++ b/x-pack/plugins/osquery/cypress/screens/live_query.ts @@ -7,7 +7,7 @@ export const AGENT_FIELD = '[data-test-subj="comboBoxInput"]'; export const ALL_AGENTS_OPTION = '[title="All agents"]'; -export const LIVE_QUERY_EDITOR = '.kibanaCodeEditor'; +export const LIVE_QUERY_EDITOR = 'kibanaCodeEditor'; export const OSQUERY_FLYOUT_BODY_EDITOR = '[data-test-subj="flyout-body-osquery"] .kibanaCodeEditor'; export const SUBMIT_BUTTON = '#submit-button'; diff --git a/x-pack/plugins/osquery/cypress/tasks/live_query.ts b/x-pack/plugins/osquery/cypress/tasks/live_query.ts index 6a342246517ac0..d57f778e2c9668 100644 --- a/x-pack/plugins/osquery/cypress/tasks/live_query.ts +++ b/x-pack/plugins/osquery/cypress/tasks/live_query.ts @@ -25,10 +25,10 @@ export const selectAllAgents = () => { }; export const clearInputQuery = () => - cy.get(LIVE_QUERY_EDITOR).click().type(`{selectall}{backspace}`); + cy.getBySel(LIVE_QUERY_EDITOR).click().type(`{selectall}{backspace}`); export const inputQuery = (query: string, options?: { parseSpecialCharSequences: boolean }) => - cy.get(LIVE_QUERY_EDITOR).type(query, options); + cy.getBySel(LIVE_QUERY_EDITOR).type(query, options); export const inputQueryInFlyout = ( query: string, diff --git a/x-pack/plugins/osquery/cypress/tasks/response_actions.ts b/x-pack/plugins/osquery/cypress/tasks/response_actions.ts index 3f59ebca9f560d..d686392431b7ac 100644 --- a/x-pack/plugins/osquery/cypress/tasks/response_actions.ts +++ b/x-pack/plugins/osquery/cypress/tasks/response_actions.ts @@ -45,6 +45,7 @@ export const checkOsqueryResponseActionsPermissions = (enabled: boolean) => { cy.getBySel('globalLoadingIndicator').should('not.exist'); closeDateTabIfVisible(); cy.getBySel('edit-rule-actions-tab').click(); + cy.getBySel('globalLoadingIndicator').should('not.exist'); cy.contains('Response actions are run on each rule execution.'); cy.getBySel(OSQUERY_RESPONSE_ACTION_ADD_BUTTON).click(); if (enabled) { diff --git a/x-pack/plugins/osquery/server/routes/live_query/create_live_query_route.ts b/x-pack/plugins/osquery/server/routes/live_query/create_live_query_route.ts index 586ebe3716a96f..fff242db7475ca 100644 --- a/x-pack/plugins/osquery/server/routes/live_query/create_live_query_route.ts +++ b/x-pack/plugins/osquery/server/routes/live_query/create_live_query_route.ts @@ -45,7 +45,9 @@ export const createLiveQueryRoute = (router: IRouter, osqueryContext: OsqueryApp const { osquery: { writeLiveQueries, runSavedQueries }, - } = await coreStartServices.capabilities.resolveCapabilities(request); + } = await coreStartServices.capabilities.resolveCapabilities(request, { + capabilityPath: 'osquery.*', + }); const isInvalid = !( writeLiveQueries || diff --git a/x-pack/plugins/profiling/kibana.jsonc b/x-pack/plugins/profiling/kibana.jsonc index 104196bababc99..296b4e40bb8228 100644 --- a/x-pack/plugins/profiling/kibana.jsonc +++ b/x-pack/plugins/profiling/kibana.jsonc @@ -1,7 +1,7 @@ { "type": "plugin", "id": "@kbn/profiling-plugin", - "owner": "@elastic/profiling-ui", + "owner": "@elastic/obs-ux-infra_services-team", "plugin": { "id": "profiling", "server": true, diff --git a/x-pack/plugins/profiling_data_access/kibana.jsonc b/x-pack/plugins/profiling_data_access/kibana.jsonc index a6bcd9f7ecff49..a2c3fb4cb267b7 100644 --- a/x-pack/plugins/profiling_data_access/kibana.jsonc +++ b/x-pack/plugins/profiling_data_access/kibana.jsonc @@ -1,7 +1,7 @@ { "type": "plugin", "id": "@kbn/profiling-data-access-plugin", - "owner": "@elastic/profiling-ui", + "owner": "@elastic/obs-ux-infra_services-team", "plugin": { "id": "profilingDataAccess", "server": true, diff --git a/x-pack/plugins/profiling_data_access/server/services/setup_state/index.ts b/x-pack/plugins/profiling_data_access/server/services/setup_state/index.ts index d11668e1af6e9b..6e1b62b28f85da 100644 --- a/x-pack/plugins/profiling_data_access/server/services/setup_state/index.ts +++ b/x-pack/plugins/profiling_data_access/server/services/setup_state/index.ts @@ -28,7 +28,7 @@ export async function getSetupState({ }: RegisterServicesParams & SetupStateParams): Promise { const kibanaInternalProfilingESClient = createProfilingEsClient({ esClient: esClient.asInternalUser, - useDefaultAuth: true, + useDefaultAuth: false, }); const profilingESClient = createProfilingEsClient({ esClient: esClient.asCurrentUser, diff --git a/x-pack/plugins/rule_registry/kibana.jsonc b/x-pack/plugins/rule_registry/kibana.jsonc index d5c867247e8110..28612bff2b9ccf 100644 --- a/x-pack/plugins/rule_registry/kibana.jsonc +++ b/x-pack/plugins/rule_registry/kibana.jsonc @@ -3,7 +3,7 @@ "id": "@kbn/rule-registry-plugin", "owner": [ "@elastic/response-ops", - "@elastic/actionable-observability" + "@elastic/obs-ux-management-team" ], "plugin": { "id": "ruleRegistry", diff --git a/x-pack/plugins/security/server/anonymous_access/anonymous_access_service.test.ts b/x-pack/plugins/security/server/anonymous_access/anonymous_access_service.test.ts index 3cee2adf03f5c3..398ff6a3c425bb 100644 --- a/x-pack/plugins/security/server/anonymous_access/anonymous_access_service.test.ts +++ b/x-pack/plugins/security/server/anonymous_access/anonymous_access_service.test.ts @@ -165,6 +165,7 @@ describe('AnonymousAccessService', () => { expect.objectContaining({ headers: {} }), { useDefaultCapabilities: true, + capabilityPath: '*', } ); }); @@ -202,6 +203,7 @@ describe('AnonymousAccessService', () => { expect.objectContaining({ headers: {} }), { useDefaultCapabilities: true, + capabilityPath: '*', } ); }); @@ -225,7 +227,7 @@ describe('AnonymousAccessService', () => { expect(startParams.capabilities.resolveCapabilities).toHaveBeenCalledTimes(1); expect(startParams.capabilities.resolveCapabilities).toHaveBeenCalledWith( expect.objectContaining({ headers: { authorization: 'Basic dXNlcjpwYXNzd29yZA==' } }), - { useDefaultCapabilities: false } + { useDefaultCapabilities: false, capabilityPath: '*' } ); }); @@ -245,7 +247,7 @@ describe('AnonymousAccessService', () => { expect(startParams.capabilities.resolveCapabilities).toHaveBeenCalledTimes(1); expect(startParams.capabilities.resolveCapabilities).toHaveBeenCalledWith( expect.objectContaining({ headers: { authorization: 'Basic dXNlcjpwYXNzd29yZA==' } }), - { useDefaultCapabilities: false } + { useDefaultCapabilities: false, capabilityPath: '*' } ); }); }); diff --git a/x-pack/plugins/security/server/anonymous_access/anonymous_access_service.ts b/x-pack/plugins/security/server/anonymous_access/anonymous_access_service.ts index 8641d242d56eb4..ad0a9b79fff461 100644 --- a/x-pack/plugins/security/server/anonymous_access/anonymous_access_service.ts +++ b/x-pack/plugins/security/server/anonymous_access/anonymous_access_service.ts @@ -121,6 +121,7 @@ export class AnonymousAccessService { try { return await capabilities.resolveCapabilities(fakeAnonymousRequest, { + capabilityPath: '*', useDefaultCapabilities, }); } catch (err) { 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 0d1009bdd4b9e5..a052d675324801 100644 --- a/x-pack/plugins/security/server/authorization/authorization_service.test.ts +++ b/x-pack/plugins/security/server/authorization/authorization_service.test.ts @@ -116,7 +116,9 @@ it(`#setup returns exposed services`, () => { expect(authorizationModeFactory).toHaveBeenCalledWith(mockLicense); expect(mockCoreSetup.capabilities.registerSwitcher).toHaveBeenCalledTimes(1); - expect(mockCoreSetup.capabilities.registerSwitcher).toHaveBeenCalledWith(expect.any(Function)); + expect(mockCoreSetup.capabilities.registerSwitcher).toHaveBeenCalledWith(expect.any(Function), { + capabilityPath: '*', + }); }); describe('#start', () => { diff --git a/x-pack/plugins/security/server/authorization/authorization_service.tsx b/x-pack/plugins/security/server/authorization/authorization_service.tsx index a2b32a0a6b13ea..10bee6309438f9 100644 --- a/x-pack/plugins/security/server/authorization/authorization_service.tsx +++ b/x-pack/plugins/security/server/authorization/authorization_service.tsx @@ -64,8 +64,11 @@ interface AuthorizationServiceSetupParams { loggers: LoggerFactory; features: FeaturesPluginSetup; kibanaIndexName: string; + getSpacesService(): SpacesService | undefined; + getCurrentUser(request: KibanaRequest): AuthenticatedUser | null; + customBranding: CustomBrandingSetup; } @@ -174,6 +177,9 @@ export class AuthorizationService { } return await disableUICapabilities.usingPrivileges(uiCapabilities); + }, + { + capabilityPath: '*', } ); diff --git a/x-pack/plugins/security/server/authorization/check_privileges.ts b/x-pack/plugins/security/server/authorization/check_privileges.ts index 0e842da4e48511..abc0b791dd49a7 100644 --- a/x-pack/plugins/security/server/authorization/check_privileges.ts +++ b/x-pack/plugins/security/server/authorization/check_privileges.ts @@ -144,13 +144,11 @@ export function checkPrivilegesFactory( const indexPrivileges = Object.entries(hasPrivilegesResponse.index ?? {}).reduce< CheckPrivilegesResponse['privileges']['elasticsearch']['index'] >((acc, [index, indexResponse]) => { - return { - ...acc, - [index]: Object.entries(indexResponse).map(([privilege, authorized]) => ({ - privilege, - authorized, - })), - }; + acc[index] = Object.entries(indexResponse).map(([privilege, authorized]) => ({ + privilege, + authorized, + })); + return acc; }, {}); // we need to filter out the non requested privileges from the response diff --git a/x-pack/plugins/security/server/authorization/disable_ui_capabilities.ts b/x-pack/plugins/security/server/authorization/disable_ui_capabilities.ts index e3cd51bc77015b..7283b955e906cc 100644 --- a/x-pack/plugins/security/server/authorization/disable_ui_capabilities.ts +++ b/x-pack/plugins/security/server/authorization/disable_ui_capabilities.ts @@ -37,10 +37,8 @@ export function disableUICapabilitiesFactory( const elasticsearchFeatureMap = elasticsearchFeatures.reduce< Record> >((acc, esFeature) => { - return { - ...acc, - [esFeature.id]: esFeature.privileges, - }; + acc[esFeature.id] = esFeature.privileges; + return acc; }, {}); const allRequiredClusterPrivileges = Array.from( @@ -59,11 +57,9 @@ export function disableUICapabilitiesFactory( return { ...acc, ...Object.entries(p.requiredIndexPrivileges!).reduce((acc2, [indexName, privileges]) => { - return { - ...acc2, - [indexName]: [...(acc[indexName] ?? []), ...privileges], - }; - }, {}), + acc2[indexName] = [...(acc[indexName] ?? []), ...privileges]; + return acc2; + }, {} as Record), }; }, {}); @@ -157,14 +153,16 @@ export function disableUICapabilitiesFactory( } const uiActions = Object.entries(uiCapabilities).reduce( - (acc, [featureId, featureUICapabilities]) => [ - ...acc, - ...flatten( - Object.entries(featureUICapabilities).map(([uiCapability, value]) => { - return getActionsForFeatureCapability(featureId, uiCapability, value); - }) - ), - ], + (acc, [featureId, featureUICapabilities]) => { + acc.push( + ...flatten( + Object.entries(featureUICapabilities).map(([uiCapability, value]) => { + return getActionsForFeatureCapability(featureId, uiCapability, value); + }) + ) + ); + return acc; + }, [] ); diff --git a/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/management.ts b/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/management.ts index 72cc28f5570b53..ef84f8696a2123 100644 --- a/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/management.ts +++ b/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/management.ts @@ -18,7 +18,8 @@ export class FeaturePrivilegeManagementBuilder extends BaseFeaturePrivilegeBuild } return Object.entries(managementSections).reduce((acc, [sectionId, items]) => { - return [...acc, ...items.map((item) => this.actions.ui.get('management', sectionId, item))]; + acc.push(...items.map((item) => this.actions.ui.get('management', sectionId, item))); + return acc; }, [] as string[]); } } diff --git a/x-pack/plugins/security/server/authorization/privileges/privileges.ts b/x-pack/plugins/security/server/authorization/privileges/privileges.ts index 20de4011c39f42..e3e151052f0562 100644 --- a/x-pack/plugins/security/server/authorization/privileges/privileges.ts +++ b/x-pack/plugins/security/server/authorization/privileges/privileges.ts @@ -37,8 +37,8 @@ export function privilegesFactory( (feature) => !feature.excludeFromBasePrivileges ); - let allActions: string[] = []; - let readActions: string[] = []; + const allActionsSet = new Set(); + const readActionsSet = new Set(); basePrivilegeFeatures.forEach((feature) => { for (const { privilegeId, privilege } of featuresService.featurePrivilegeIterator(feature, { @@ -47,15 +47,17 @@ export function privilegesFactory( predicate: (pId, featurePrivilege) => !featurePrivilege.excludeFromBasePrivileges, })) { const privilegeActions = featurePrivilegeBuilder.getActions(privilege, feature); - allActions = [...allActions, ...privilegeActions]; - if (privilegeId === 'read') { - readActions = [...readActions, ...privilegeActions]; - } + privilegeActions.forEach((action) => { + allActionsSet.add(action); + if (privilegeId === 'read') { + readActionsSet.add(action); + } + }); } }); - allActions = uniq(allActions); - readActions = uniq(readActions); + const allActions = [...allActionsSet]; + const readActions = [...readActionsSet]; const featurePrivileges: Record> = {}; for (const feature of features) { diff --git a/x-pack/plugins/security/server/routes/authorization/privileges/get.ts b/x-pack/plugins/security/server/routes/authorization/privileges/get.ts index 8817fca4831ae4..1d278aa676ac38 100644 --- a/x-pack/plugins/security/server/routes/authorization/privileges/get.ts +++ b/x-pack/plugins/security/server/routes/authorization/privileges/get.ts @@ -38,12 +38,10 @@ export function defineGetPrivilegesRoutes({ router, authz }: RouteDefinitionPara space: Object.keys(privileges.space), features: Object.entries(privileges.features).reduce( (acc, [featureId, featurePrivileges]) => { - return { - ...acc, - [featureId]: Object.keys(featurePrivileges), - }; + acc[featureId] = Object.keys(featurePrivileges); + return acc; }, - {} + {} as Record ), reserved: Object.keys(privileges.reserved), }; diff --git a/x-pack/plugins/security/server/saved_objects/ensure_authorized.ts b/x-pack/plugins/security/server/saved_objects/ensure_authorized.ts index e945848d711054..79e15be6507732 100644 --- a/x-pack/plugins/security/server/saved_objects/ensure_authorized.ts +++ b/x-pack/plugins/security/server/saved_objects/ensure_authorized.ts @@ -104,7 +104,10 @@ export async function ensureAuthorized( // Neither fully nor partially authorized. Bail with error. const uniqueUnauthorizedPrivileges = [...missingPrivileges.entries()].reduce( - (acc, [, privilegeSet]) => new Set([...acc, ...privilegeSet]), + (acc, [, privilegeSet]) => { + privilegeSet.forEach((entry) => acc.add(entry)); + return acc; + }, new Set() ); const targetTypesAndActions = [...uniqueUnauthorizedPrivileges] diff --git a/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.ts b/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.ts index 18c6ae824584c9..1e9e25b6b800d8 100644 --- a/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.ts +++ b/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.ts @@ -81,10 +81,13 @@ export class SecureSpacesClientWrapper implements ISpacesClient { // Collect all privileges which need to be checked const allPrivileges = Object.entries(PURPOSE_PRIVILEGE_MAP).reduce( - (acc, [getSpacesPurpose, privilegeFactory]) => - !includeAuthorizedPurposes && getSpacesPurpose !== purpose - ? acc - : { ...acc, [getSpacesPurpose]: privilegeFactory(this.authorization) }, + (acc, [getSpacesPurpose, privilegeFactory]) => { + if (!includeAuthorizedPurposes && getSpacesPurpose !== purpose) { + return acc; + } + acc[getSpacesPurpose as GetAllSpacesPurpose] = privilegeFactory(this.authorization); + return acc; + }, {} as Record ); @@ -117,7 +120,8 @@ export class SecureSpacesClientWrapper implements ISpacesClient { const requiredActions = privilegeFactory(this.authorization); const hasAllRequired = checkHasAllRequired(space, requiredActions); hasAnyAuthorization = hasAnyAuthorization || hasAllRequired; - return { ...acc, [purposeKey]: hasAllRequired }; + acc[purposeKey as GetAllSpacesPurpose] = hasAllRequired; + return acc; }, {} as Record ); diff --git a/x-pack/plugins/security/server/usage_collector/security_usage_collector.ts b/x-pack/plugins/security/server/usage_collector/security_usage_collector.ts index f73ac0bbb25a3b..58599433914424 100644 --- a/x-pack/plugins/security/server/usage_collector/security_usage_collector.ts +++ b/x-pack/plugins/security/server/usage_collector/security_usage_collector.ts @@ -160,12 +160,7 @@ export function registerSecurityUsageCollector({ usageCollection, config, licens const loginSelectorEnabled = config.authc.selector.enabled; const authProviderCount = config.authc.sortedProviders.length; const enabledAuthProviders = [ - ...new Set( - config.authc.sortedProviders.reduce( - (acc, provider) => [...acc, provider.type], - [] as string[] - ) - ), + ...new Set(config.authc.sortedProviders.map((provider) => provider.type)), ]; const accessAgreementEnabled = allowAccessAgreement && diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/error_schema.mock.ts b/x-pack/plugins/security_solution/common/api/detection_engine/model/error_schema.mock.ts index 22c507804e1d8a..b52f61febbafbe 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/model/error_schema.mock.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/model/error_schema.mock.ts @@ -4,9 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -// TODO https://github.com/elastic/security-team/issues/7491 -// eslint-disable-next-line no-restricted-imports -import type { ErrorSchema } from './error_schema_legacy'; +import type { ErrorSchema } from './error_schema.gen'; export const getErrorSchemaMock = ( id: string = '819eded6-e9c8-445b-a647-519aea39e063' diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/index.ts b/x-pack/plugins/security_solution/common/api/detection_engine/model/index.ts index 0d243fc201fb96..f5c8440a071489 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/model/index.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/model/index.ts @@ -8,12 +8,8 @@ export * from './alerts'; export * from './rule_response_actions'; export * from './rule_schema'; -// TODO https://github.com/elastic/security-team/issues/7491 -// eslint-disable-next-line no-restricted-imports -export * from './error_schema_legacy'; -export * from './pagination'; +export * from './error_schema.gen'; +export * from './pagination.gen'; export * from './schemas'; -// TODO https://github.com/elastic/security-team/issues/7491 -// eslint-disable-next-line no-restricted-imports -export * from './sorting_legacy'; +export * from './sorting.gen'; export * from './warning_schema.gen'; diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/pagination.gen.ts b/x-pack/plugins/security_solution/common/api/detection_engine/model/pagination.gen.ts new file mode 100644 index 00000000000000..0a7336b8f78c38 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/detection_engine/model/pagination.gen.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + */ + +/** + * Page number + */ +export type Page = z.infer; +export const Page = z.number().int().min(1); + +/** + * Number of items per page + */ +export type PerPage = z.infer; +export const PerPage = z.number().int().min(0); + +export type PaginationResult = z.infer; +export const PaginationResult = z.object({ + page: Page, + per_page: PerPage, + /** + * Total number of items + */ + total: z.number().int().min(0), +}); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/pagination.schema.yaml b/x-pack/plugins/security_solution/common/api/detection_engine/model/pagination.schema.yaml new file mode 100644 index 00000000000000..3afccce86e3296 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/detection_engine/model/pagination.schema.yaml @@ -0,0 +1,31 @@ +openapi: 3.0.0 +info: + title: Pagination Schema + version: 'not applicable' +paths: {} +components: + x-codegen-enabled: true + schemas: + Page: + type: integer + minimum: 1 + description: Page number + PerPage: + type: integer + minimum: 0 + description: Number of items per page + PaginationResult: + type: object + properties: + page: + $ref: '#/components/schemas/Page' + per_page: + $ref: '#/components/schemas/PerPage' + total: + type: integer + minimum: 0 + description: Total number of items + required: + - page + - per_page + - total diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/pagination.ts b/x-pack/plugins/security_solution/common/api/detection_engine/model/pagination.ts deleted file mode 100644 index bed2cade86df4a..00000000000000 --- a/x-pack/plugins/security_solution/common/api/detection_engine/model/pagination.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as t from 'io-ts'; -import { PositiveInteger, PositiveIntegerGreaterThanZero } from '@kbn/securitysolution-io-ts-types'; - -export type Page = t.TypeOf; -export const Page = PositiveIntegerGreaterThanZero; - -export type PageOrUndefined = t.TypeOf; -export const PageOrUndefined = t.union([Page, t.undefined]); - -export type PerPage = t.TypeOf; -export const PerPage = PositiveInteger; - -export type PerPageOrUndefined = t.TypeOf; -export const PerPageOrUndefined = t.union([PerPage, t.undefined]); - -export type PaginationResult = t.TypeOf; -export const PaginationResult = t.type({ - page: Page, - per_page: PerPage, - total: PositiveInteger, -}); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_response_actions/index.ts b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_response_actions/index.ts index ccaf290dc5d338..e9956d88eb45ae 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_response_actions/index.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_response_actions/index.ts @@ -5,7 +5,4 @@ * 2.0. */ -// TODO https://github.com/elastic/security-team/issues/7491 -// eslint-disable-next-line no-restricted-imports -export { RESPONSE_ACTION_TYPES, SUPPORTED_RESPONSE_ACTION_TYPES } from './response_actions_legacy'; export * from './response_actions.gen'; diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/common_attributes.gen.ts b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/common_attributes.gen.ts index 0d62dfd9c21f30..79ad21ddfb0090 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/common_attributes.gen.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/common_attributes.gen.ts @@ -367,26 +367,38 @@ export const RuleActionFrequency = z.object({ throttle: RuleActionThrottle.nullable(), }); +export type RuleActionAlertsFilter = z.infer; +export const RuleActionAlertsFilter = z.object({}).catchall(z.unknown()); + +/** + * Object containing the allowed connector fields, which varies according to the connector type. + */ +export type RuleActionParams = z.infer; +export const RuleActionParams = z.object({}).catchall(z.unknown()); + +/** + * Optionally groups actions by use cases. Use `default` for alert notifications. + */ +export type RuleActionGroup = z.infer; +export const RuleActionGroup = z.string(); + +/** + * The connector ID. + */ +export type RuleActionId = z.infer; +export const RuleActionId = z.string(); + export type RuleAction = z.infer; export const RuleAction = z.object({ /** * The action type used for sending notifications. */ action_type_id: z.string(), - /** - * Optionally groups actions by use cases. Use `default` for alert notifications. - */ - group: z.string(), - /** - * The connector ID. - */ - id: z.string(), - /** - * Object containing the allowed connector fields, which varies according to the connector type. - */ - params: z.object({}).catchall(z.unknown()), + group: RuleActionGroup, + id: RuleActionId, + params: RuleActionParams, uuid: NonEmptyString.optional(), - alerts_filter: z.object({}).catchall(z.unknown()).optional(), + alerts_filter: RuleActionAlertsFilter.optional(), frequency: RuleActionFrequency.optional(), }); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/common_attributes.schema.yaml b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/common_attributes.schema.yaml index 921f9350550b6c..ad2bfaf76c4c01 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/common_attributes.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/common_attributes.schema.yaml @@ -397,6 +397,23 @@ components: - notifyWhen - throttle + RuleActionAlertsFilter: + type: object + additionalProperties: true + + RuleActionParams: + type: object + description: Object containing the allowed connector fields, which varies according to the connector type. + additionalProperties: true + + RuleActionGroup: + type: string + description: Optionally groups actions by use cases. Use `default` for alert notifications. + + RuleActionId: + type: string + description: The connector ID. + RuleAction: type: object properties: @@ -404,20 +421,15 @@ components: type: string description: The action type used for sending notifications. group: - type: string - description: Optionally groups actions by use cases. Use `default` for alert notifications. + $ref: '#/components/schemas/RuleActionGroup' id: - type: string - description: The connector ID. + $ref: '#/components/schemas/RuleActionId' params: - type: object - description: Object containing the allowed connector fields, which varies according to the connector type. - additionalProperties: true + $ref: '#/components/schemas/RuleActionParams' uuid: $ref: '#/components/schemas/NonEmptyString' alerts_filter: - type: object - additionalProperties: true + $ref: '#/components/schemas/RuleActionAlertsFilter' frequency: $ref: '#/components/schemas/RuleActionFrequency' required: diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_request_schema.test.ts b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_request_schema.test.ts index abbfa4903ea31e..062c9133544049 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_request_schema.test.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_request_schema.test.ts @@ -25,7 +25,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"name: Required, description: Required, risk_score: Required, severity: Required, type: Invalid literal value, expected \\"eql\\", and 52 more"` + ); }); test('strips any unknown values', () => { @@ -46,7 +48,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"name: Required, description: Required, risk_score: Required, severity: Required, type: Invalid literal value, expected \\"eql\\", and 52 more"` + ); }); test('[rule_id, description] does not validate', () => { @@ -57,7 +61,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"name: Required, risk_score: Required, severity: Required, type: Invalid literal value, expected \\"eql\\", query: Required, and 44 more"` + ); }); test('[rule_id, description, from] does not validate', () => { @@ -69,7 +75,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"name: Required, risk_score: Required, severity: Required, type: Invalid literal value, expected \\"eql\\", query: Required, and 44 more"` + ); }); test('[rule_id, description, from, to] does not validate', () => { @@ -82,7 +90,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"name: Required, risk_score: Required, severity: Required, type: Invalid literal value, expected \\"eql\\", query: Required, and 44 more"` + ); }); test('[rule_id, description, from, to, name] does not validate', () => { @@ -96,7 +106,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"risk_score: Required, severity: Required, type: Invalid literal value, expected \\"eql\\", query: Required, language: Invalid literal value, expected \\"eql\\", and 36 more"` + ); }); test('[rule_id, description, from, to, name, severity] does not validate', () => { @@ -111,7 +123,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"risk_score: Required, type: Invalid literal value, expected \\"eql\\", query: Required, language: Invalid literal value, expected \\"eql\\", risk_score: Required, and 28 more"` + ); }); test('[rule_id, description, from, to, name, severity, type] does not validate', () => { @@ -127,7 +141,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"risk_score: Required, type: Invalid literal value, expected \\"eql\\", query: Required, language: Invalid literal value, expected \\"eql\\", risk_score: Required, and 27 more"` + ); }); test('[rule_id, description, from, to, name, severity, type, interval] does not validate', () => { @@ -144,7 +160,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"risk_score: Required, type: Invalid literal value, expected \\"eql\\", query: Required, language: Invalid literal value, expected \\"eql\\", risk_score: Required, and 27 more"` + ); }); test('[rule_id, description, from, to, name, severity, type, interval, index] does not validate', () => { @@ -162,7 +180,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"risk_score: Required, type: Invalid literal value, expected \\"eql\\", query: Required, language: Invalid literal value, expected \\"eql\\", risk_score: Required, and 27 more"` + ); }); test('[rule_id, description, from, to, name, severity, type, query, index, interval] does validate', () => { @@ -202,7 +222,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"risk_score: Required, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", risk_score: Required, risk_score: Required, and 22 more"` + ); }); test('[rule_id, description, from, to, index, name, severity, interval, type, query, language, risk_score] does validate', () => { @@ -368,7 +390,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"references.0: Expected string, received number, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", references.0: Expected string, received number, references.0: Expected string, received number, and 22 more"` + ); }); test('indexes cannot be numbers', () => { @@ -379,7 +403,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", index.0: Expected string, received number, index.0: Expected string, received number, type: Invalid literal value, expected \\"saved_query\\", and 20 more"` + ); }); test('saved_query type can have filters with it', () => { @@ -401,7 +427,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", filters: Expected array, received string, filters: Expected array, received string, type: Invalid literal value, expected \\"saved_query\\", and 20 more"` + ); }); test('language validates with kuery', () => { @@ -434,7 +462,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", language: Invalid enum value. Expected 'kuery' | 'lucene', received 'something-made-up', type: Invalid literal value, expected \\"saved_query\\", saved_id: Required, and 19 more"` + ); }); test('max_signals cannot be negative', () => { @@ -493,7 +523,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"tags.0: Expected string, received number, tags.1: Expected string, received number, tags.2: Expected string, received number, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", and 38 more"` + ); }); test('You cannot send in an array of threat that are missing "framework"', () => { @@ -519,7 +551,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"threat.0.framework: Required, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", threat.0.framework: Required, threat.0.framework: Required, and 22 more"` + ); }); test('You cannot send in an array of threat that are missing "tactic"', () => { @@ -541,7 +575,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"threat.0.tactic: Required, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", threat.0.tactic: Required, threat.0.tactic: Required, and 22 more"` + ); }); test('You can send in an array of threat that are missing "technique"', () => { @@ -583,7 +619,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"false_positives.0: Expected string, received number, false_positives.1: Expected string, received number, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", false_positives.0: Expected string, received number, and 30 more"` + ); }); test('You cannot set the risk_score to 101', () => { @@ -655,7 +693,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"meta: Expected object, received string, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", meta: Expected object, received string, meta: Expected object, received string, and 22 more"` + ); }); test('You can omit the query string when filters are present', () => { @@ -690,7 +730,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"severity: Invalid enum value. Expected 'low' | 'medium' | 'high' | 'critical', received 'junk', type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", severity: Invalid enum value. Expected 'low' | 'medium' | 'high' | 'critical', received 'junk', severity: Invalid enum value. Expected 'low' | 'medium' | 'high' | 'critical', received 'junk', and 22 more"` + ); }); test('You cannot send in an array of actions that are missing "group"', () => { @@ -701,7 +743,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"actions.0.group: Required, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", actions.0.group: Required, actions.0.group: Required, and 22 more"` + ); }); test('You cannot send in an array of actions that are missing "id"', () => { @@ -712,7 +756,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"actions.0.id: Required, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", actions.0.id: Required, actions.0.id: Required, and 22 more"` + ); }); test('You cannot send in an array of actions that are missing "action_type_id"', () => { @@ -723,7 +769,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"actions.0.action_type_id: Required, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", actions.0.action_type_id: Required, actions.0.action_type_id: Required, and 22 more"` + ); }); test('You cannot send in an array of actions that are missing "params"', () => { @@ -734,7 +782,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"actions.0.params: Required, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", actions.0.params: Required, actions.0.params: Required, and 22 more"` + ); }); test('You cannot send in an array of actions that are including "actionTypeId"', () => { @@ -752,7 +802,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"actions.0.action_type_id: Required, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", actions.0.action_type_id: Required, actions.0.action_type_id: Required, and 22 more"` + ); }); describe('note', () => { @@ -788,7 +840,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"note: Expected string, received object, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", note: Expected string, received object, note: Expected string, received object, and 22 more"` + ); }); test('empty name is not valid', () => { @@ -872,7 +926,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", type: Invalid literal value, expected \\"query\\", saved_id: Required, type: Invalid literal value, expected \\"threshold\\", and 14 more"` + ); }); test('threshold is required when type is threshold and will not validate without it', () => { @@ -880,7 +936,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", type: Invalid literal value, expected \\"query\\", type: Invalid literal value, expected \\"saved_query\\", saved_id: Required, and 14 more"` + ); }); test('threshold rules fail validation if threshold is not greater than 0', () => { @@ -958,7 +1016,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"exceptions_list.0.list_id: Required, exceptions_list.0.type: Required, exceptions_list.0.namespace_type: Invalid enum value. Expected 'agnostic' | 'single', received 'not a namespace type', type: Invalid literal value, expected \\"eql\\", query: Required, and 43 more"` + ); }); test('[rule_id, description, from, to, index, name, severity, interval, type, filters, risk_score, note, and non-existent exceptions_list] does validate with empty exceptions_list', () => { @@ -999,7 +1059,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", type: Invalid literal value, expected \\"query\\", type: Invalid literal value, expected \\"saved_query\\", saved_id: Required, and 14 more"` + ); }); test('fails validation when threat_mapping is an empty array', () => { @@ -1068,7 +1130,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", data_view_id: Expected string, received number, data_view_id: Expected string, received number, type: Invalid literal value, expected \\"saved_query\\", and 20 more"` + ); }); test('it should validate a type of "query" with "data_view_id" defined', () => { @@ -1131,7 +1195,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"investigation_fields.field_names: Required, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", investigation_fields.field_names: Required, investigation_fields.field_names: Required, and 22 more"` + ); }); test('You can send in investigation_fields', () => { @@ -1166,7 +1232,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"investigation_fields.field_names.0: Expected string, received number, investigation_fields.field_names.1: Expected string, received number, investigation_fields.field_names.2: Expected string, received number, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", and 38 more"` + ); }); test('You cannot send in investigation_fields without specifying fields', () => { @@ -1177,7 +1245,9 @@ describe('rules schema', () => { const result = RuleCreateProps.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"investigation_fields.field_names: Required, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", investigation_fields.field_names: Required, investigation_fields.field_names: Required, and 22 more"` + ); }); }); }); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_response_schema.test.ts b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_response_schema.test.ts index e8573502cb662f..d1432e5a673520 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_response_schema.test.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_response_schema.test.ts @@ -40,7 +40,9 @@ describe('Rule response schema', () => { const result = RuleResponse.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", type: Invalid literal value, expected \\"query\\", type: Invalid literal value, expected \\"saved_query\\", saved_id: Required, and 15 more"` + ); }); test('it should validate a type of "query" with a saved_id together', () => { @@ -68,7 +70,9 @@ describe('Rule response schema', () => { const result = RuleResponse.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", type: Invalid literal value, expected \\"query\\", saved_id: Required, type: Invalid literal value, expected \\"threshold\\", and 14 more"` + ); }); test('it should validate a type of "timeline_id" if there is a "timeline_title" dependent', () => { @@ -98,7 +102,9 @@ describe('Rule response schema', () => { const result = RuleResponse.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"exceptions_list: Expected array, received string, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", exceptions_list: Expected array, received string, exceptions_list: Expected array, received string, and 22 more"` + ); }); }); @@ -232,6 +238,8 @@ describe('investigation_fields', () => { const result = RuleResponse.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('Invalid input'); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"investigation_fields: Expected object, received string, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", investigation_fields: Expected object, received string, investigation_fields: Expected object, received string, and 22 more"` + ); }); }); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_response_actions/response_actions_legacy.ts b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema_legacy/response_actions.ts similarity index 82% rename from x-pack/plugins/security_solution/common/api/detection_engine/model/rule_response_actions/response_actions_legacy.ts rename to x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema_legacy/response_actions.ts index 6947953b4d65d0..8f176a99080416 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_response_actions/response_actions_legacy.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema_legacy/response_actions.ts @@ -4,18 +4,9 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - 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'; -import { ResponseActionTypesEnum } from './response_actions.gen'; - -export const RESPONSE_ACTION_TYPES = { - OSQUERY: ResponseActionTypesEnum['.osquery'], - ENDPOINT: ResponseActionTypesEnum['.endpoint'], -} as const; - -export const SUPPORTED_RESPONSE_ACTION_TYPES = Object.values(RESPONSE_ACTION_TYPES); // to enable using RESPONSE_ACTION_API_COMMANDS_NAMES as a type function keyObject(arr: T): { [K in T[number]]: null } { @@ -47,13 +38,13 @@ export const OsqueryParamsCamelCase = t.type({ // When we create new response action types, create a union of types export type RuleResponseOsqueryAction = t.TypeOf; export const RuleResponseOsqueryAction = t.strict({ - actionTypeId: t.literal(RESPONSE_ACTION_TYPES.OSQUERY), + actionTypeId: t.literal('.osquery'), params: OsqueryParamsCamelCase, }); export type RuleResponseEndpointAction = t.TypeOf; export const RuleResponseEndpointAction = t.strict({ - actionTypeId: t.literal(RESPONSE_ACTION_TYPES.ENDPOINT), + actionTypeId: t.literal('.endpoint'), params: EndpointParams, }); @@ -67,12 +58,12 @@ export const ResponseActionRuleParamsOrUndefined = t.union([ // When we create new response action types, create a union of types const OsqueryResponseAction = t.strict({ - action_type_id: t.literal(RESPONSE_ACTION_TYPES.OSQUERY), + action_type_id: t.literal('.osquery'), params: OsqueryParams, }); const EndpointResponseAction = t.strict({ - action_type_id: t.literal(RESPONSE_ACTION_TYPES.ENDPOINT), + action_type_id: t.literal('.endpoint'), params: EndpointParams, }); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema_legacy/rule_schemas.ts b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema_legacy/rule_schemas.ts index e95fa38e0d2e65..4ec9ca19ee3993 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema_legacy/rule_schemas.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema_legacy/rule_schemas.ts @@ -26,20 +26,10 @@ import { threat_mapping, threat_query, } from '@kbn/securitysolution-io-ts-alerting-types'; +import { PositiveInteger } from '@kbn/securitysolution-io-ts-types'; +import { ResponseActionArray } from './response_actions'; -import { RuleExecutionSummary } from '../../rule_monitoring/model'; -// eslint-disable-next-line no-restricted-imports -import { ResponseActionArray } from '../rule_response_actions/response_actions_legacy'; - -import { - anomaly_threshold, - created_at, - created_by, - revision, - saved_id, - updated_at, - updated_by, -} from '../schemas'; +import { anomaly_threshold, saved_id } from '../schemas'; import { AlertsIndex, @@ -51,10 +41,7 @@ import { InvestigationFields, InvestigationGuide, IsRuleEnabled, - IsRuleImmutable, MaxSignals, - RelatedIntegrationArray, - RequiredFieldArray, RuleAuthorArray, RuleDescription, RuleFalsePositiveArray, @@ -63,7 +50,6 @@ import { RuleMetadata, RuleName, RuleNameOverride, - RuleObjectId, RuleQuery, RuleReferenceArray, RuleSignatureId, @@ -72,7 +58,6 @@ import { SavedObjectResolveAliasPurpose, SavedObjectResolveAliasTargetId, SavedObjectResolveOutcome, - SetupGuide, ThreatArray, TimelineTemplateId, TimelineTemplateTitle, @@ -186,28 +171,24 @@ export const baseSchema = buildRuleSchemas({ }, }); -const responseRequiredFields = { - id: RuleObjectId, - rule_id: RuleSignatureId, - immutable: IsRuleImmutable, - updated_at, - updated_by, - created_at, - created_by, - revision, - - // NOTE: For now, Related Integrations, Required Fields and Setup Guide are supported for prebuilt - // rules only. We don't want to allow users to edit these 3 fields via the API. If we added them - // to baseParams.defaultable, they would become a part of the request schema as optional fields. - // This is why we add them here, in order to add them only to the response schema. - related_integrations: RelatedIntegrationArray, - required_fields: RequiredFieldArray, - setup: SetupGuide, -}; - -const responseOptionalFields = { - execution_summary: RuleExecutionSummary, -}; +export type DurationMetric = t.TypeOf; +export const DurationMetric = PositiveInteger; + +export type RuleExecutionMetrics = t.TypeOf; + +/** + @property total_search_duration_ms - "total time spent performing ES searches as measured by Kibana; + includes network latency and time spent serializing/deserializing request/response", + @property total_indexing_duration_ms - "total time spent indexing documents during current rule execution cycle", + @property total_enrichment_duration_ms - total time spent enriching documents during current rule execution cycle + @property execution_gap_duration_s - "duration in seconds of execution gap" +*/ +export const RuleExecutionMetrics = t.partial({ + total_search_duration_ms: DurationMetric, + total_indexing_duration_ms: DurationMetric, + total_enrichment_duration_ms: DurationMetric, + execution_gap_duration_s: DurationMetric, +}); export type BaseCreateProps = t.TypeOf; export const BaseCreateProps = baseSchema.create; @@ -225,36 +206,9 @@ export const SharedCreateProps = t.intersection([ t.exact(t.partial({ rule_id: RuleSignatureId })), ]); -type SharedUpdateProps = t.TypeOf; -const SharedUpdateProps = t.intersection([ - baseSchema.create, - t.exact(t.partial({ rule_id: RuleSignatureId })), - t.exact(t.partial({ id: RuleObjectId })), -]); - -type SharedPatchProps = t.TypeOf; -const SharedPatchProps = t.intersection([ - baseSchema.patch, - t.exact(t.partial({ rule_id: RuleSignatureId, id: RuleObjectId })), -]); - -export type SharedResponseProps = t.TypeOf; -export const SharedResponseProps = t.intersection([ - baseSchema.response, - t.exact(t.type(responseRequiredFields)), - t.exact(t.partial(responseOptionalFields)), -]); - // ------------------------------------------------------------------------------------------------- // EQL rule schema -export enum QueryLanguage { - 'kuery' = 'kuery', - 'lucene' = 'lucene', - 'eql' = 'eql', - 'esql' = 'esql', -} - export type KqlQueryLanguage = t.TypeOf; export const KqlQueryLanguage = t.keyof({ kuery: null, lucene: null }); @@ -278,21 +232,6 @@ const eqlSchema = buildRuleSchemas({ defaultable: {}, }); -export type EqlRule = t.TypeOf; -export const EqlRule = t.intersection([SharedResponseProps, eqlSchema.response]); - -export type EqlRuleCreateProps = t.TypeOf; -export const EqlRuleCreateProps = t.intersection([SharedCreateProps, eqlSchema.create]); - -export type EqlRuleUpdateProps = t.TypeOf; -export const EqlRuleUpdateProps = t.intersection([SharedUpdateProps, eqlSchema.create]); - -export type EqlRulePatchProps = t.TypeOf; -export const EqlRulePatchProps = t.intersection([SharedPatchProps, eqlSchema.patch]); - -export type EqlPatchParams = t.TypeOf; -export const EqlPatchParams = eqlSchema.patch; - // ------------------------------------------------------------------------------------------------- // ES|QL rule schema @@ -309,21 +248,6 @@ const esqlSchema = buildRuleSchemas({ defaultable: {}, }); -export type EsqlRule = t.TypeOf; -export const EsqlRule = t.intersection([SharedResponseProps, esqlSchema.response]); - -export type EsqlRuleCreateProps = t.TypeOf; -export const EsqlRuleCreateProps = t.intersection([SharedCreateProps, esqlSchema.create]); - -export type EsqlRuleUpdateProps = t.TypeOf; -export const EsqlRuleUpdateProps = t.intersection([SharedUpdateProps, esqlSchema.create]); - -export type EsqlRulePatchProps = t.TypeOf; -export const EsqlRulePatchProps = t.intersection([SharedPatchProps, esqlSchema.patch]); - -export type EsqlPatchParams = t.TypeOf; -export const EsqlPatchParams = esqlSchema.patch; - // ------------------------------------------------------------------------------------------------- // Indicator Match rule schema @@ -351,30 +275,6 @@ const threatMatchSchema = buildRuleSchemas({ }, }); -export type ThreatMatchRule = t.TypeOf; -export const ThreatMatchRule = t.intersection([SharedResponseProps, threatMatchSchema.response]); - -export type ThreatMatchRuleCreateProps = t.TypeOf; -export const ThreatMatchRuleCreateProps = t.intersection([ - SharedCreateProps, - threatMatchSchema.create, -]); - -export type ThreatMatchRuleUpdateProps = t.TypeOf; -export const ThreatMatchRuleUpdateProps = t.intersection([ - SharedUpdateProps, - threatMatchSchema.create, -]); - -export type ThreatMatchRulePatchProps = t.TypeOf; -export const ThreatMatchRulePatchProps = t.intersection([ - SharedPatchProps, - threatMatchSchema.patch, -]); - -export type ThreatMatchPatchParams = t.TypeOf; -export const ThreatMatchPatchParams = threatMatchSchema.patch; - // ------------------------------------------------------------------------------------------------- // Custom Query rule schema @@ -396,21 +296,6 @@ const querySchema = buildRuleSchemas({ }, }); -export type QueryRule = t.TypeOf; -export const QueryRule = t.intersection([SharedResponseProps, querySchema.response]); - -export type QueryRuleCreateProps = t.TypeOf; -export const QueryRuleCreateProps = t.intersection([SharedCreateProps, querySchema.create]); - -export type QueryRuleUpdateProps = t.TypeOf; -export const QueryRuleUpdateProps = t.intersection([SharedUpdateProps, querySchema.create]); - -export type QueryRulePatchProps = t.TypeOf; -export const QueryRulePatchProps = t.intersection([SharedPatchProps, querySchema.patch]); - -export type QueryPatchParams = t.TypeOf; -export const QueryPatchParams = querySchema.patch; - // ------------------------------------------------------------------------------------------------- // Saved Query rule schema @@ -434,27 +319,6 @@ const savedQuerySchema = buildRuleSchemas({ }, }); -export type SavedQueryRule = t.TypeOf; -export const SavedQueryRule = t.intersection([SharedResponseProps, savedQuerySchema.response]); - -export type SavedQueryRuleCreateProps = t.TypeOf; -export const SavedQueryRuleCreateProps = t.intersection([ - SharedCreateProps, - savedQuerySchema.create, -]); - -export type SavedQueryRuleUpdateProps = t.TypeOf; -export const SavedQueryRuleUpdateProps = t.intersection([ - SharedUpdateProps, - savedQuerySchema.create, -]); - -export type SavedQueryRulePatchProps = t.TypeOf; -export const SavedQueryRulePatchProps = t.intersection([SharedPatchProps, savedQuerySchema.patch]); - -export type SavedQueryPatchParams = t.TypeOf; -export const SavedQueryPatchParams = savedQuerySchema.patch; - // ------------------------------------------------------------------------------------------------- // Threshold rule schema @@ -475,21 +339,6 @@ const thresholdSchema = buildRuleSchemas({ }, }); -export type ThresholdRule = t.TypeOf; -export const ThresholdRule = t.intersection([SharedResponseProps, thresholdSchema.response]); - -export type ThresholdRuleCreateProps = t.TypeOf; -export const ThresholdRuleCreateProps = t.intersection([SharedCreateProps, thresholdSchema.create]); - -export type ThresholdRuleUpdateProps = t.TypeOf; -export const ThresholdRuleUpdateProps = t.intersection([SharedUpdateProps, thresholdSchema.create]); - -export type ThresholdRulePatchProps = t.TypeOf; -export const ThresholdRulePatchProps = t.intersection([SharedPatchProps, thresholdSchema.patch]); - -export type ThresholdPatchParams = t.TypeOf; -export const ThresholdPatchParams = thresholdSchema.patch; - // ------------------------------------------------------------------------------------------------- // Machine Learning rule schema @@ -503,33 +352,6 @@ const machineLearningSchema = buildRuleSchemas({ defaultable: {}, }); -export type MachineLearningRule = t.TypeOf; -export const MachineLearningRule = t.intersection([ - SharedResponseProps, - machineLearningSchema.response, -]); - -export type MachineLearningRuleCreateProps = t.TypeOf; -export const MachineLearningRuleCreateProps = t.intersection([ - SharedCreateProps, - machineLearningSchema.create, -]); - -export type MachineLearningRuleUpdateProps = t.TypeOf; -export const MachineLearningRuleUpdateProps = t.intersection([ - SharedUpdateProps, - machineLearningSchema.create, -]); - -export type MachineLearningRulePatchProps = t.TypeOf; -export const MachineLearningRulePatchProps = t.intersection([ - SharedPatchProps, - machineLearningSchema.patch, -]); - -export type MachineLearningPatchParams = t.TypeOf; -export const MachineLearningPatchParams = machineLearningSchema.patch; - // ------------------------------------------------------------------------------------------------- // New Terms rule schema @@ -550,21 +372,6 @@ const newTermsSchema = buildRuleSchemas({ }, }); -export type NewTermsRule = t.TypeOf; -export const NewTermsRule = t.intersection([SharedResponseProps, newTermsSchema.response]); - -export type NewTermsRuleCreateProps = t.TypeOf; -export const NewTermsRuleCreateProps = t.intersection([SharedCreateProps, newTermsSchema.create]); - -export type NewTermsRuleUpdateProps = t.TypeOf; -export const NewTermsRuleUpdateProps = t.intersection([SharedUpdateProps, newTermsSchema.create]); - -export type NewTermsRulePatchProps = t.TypeOf; -export const NewTermsRulePatchProps = t.intersection([SharedPatchProps, newTermsSchema.patch]); - -export type NewTermsPatchParams = t.TypeOf; -export const NewTermsPatchParams = newTermsSchema.patch; - // ------------------------------------------------------------------------------------------------- // Combined type specific schemas @@ -579,42 +386,3 @@ export const TypeSpecificCreateProps = t.union([ machineLearningSchema.create, newTermsSchema.create, ]); - -export type TypeSpecificPatchProps = t.TypeOf; -export const TypeSpecificPatchProps = t.union([ - eqlSchema.patch, - esqlSchema.patch, - threatMatchSchema.patch, - querySchema.patch, - savedQuerySchema.patch, - thresholdSchema.patch, - machineLearningSchema.patch, - newTermsSchema.patch, -]); - -export type TypeSpecificResponse = t.TypeOf; -export const TypeSpecificResponse = t.union([ - eqlSchema.response, - esqlSchema.response, - threatMatchSchema.response, - querySchema.response, - savedQuerySchema.response, - thresholdSchema.response, - machineLearningSchema.response, - newTermsSchema.response, -]); - -// ------------------------------------------------------------------------------------------------- -// Final combined schemas - -export type RuleCreateProps = t.TypeOf; -export const RuleCreateProps = t.intersection([TypeSpecificCreateProps, SharedCreateProps]); - -export type RuleUpdateProps = t.TypeOf; -export const RuleUpdateProps = t.intersection([TypeSpecificCreateProps, SharedUpdateProps]); - -export type RulePatchProps = t.TypeOf; -export const RulePatchProps = t.intersection([TypeSpecificPatchProps, SharedPatchProps]); - -export type RuleResponse = t.TypeOf; -export const RuleResponse = t.intersection([TypeSpecificResponse, SharedResponseProps]); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/schemas.ts b/x-pack/plugins/security_solution/common/api/detection_engine/model/schemas.ts index 9c325d1e70fc03..35a394edcb6adf 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/model/schemas.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/model/schemas.ts @@ -33,12 +33,6 @@ export type Status = t.TypeOf; export const conflicts = t.keyof({ abort: null, proceed: null }); -export const queryFilter = t.string; -export type QueryFilter = t.TypeOf; - -export const queryFilterOrUndefined = t.union([queryFilter, t.undefined]); -export type QueryFilterOrUndefined = t.TypeOf; - export const signal_ids = t.array(t.string); export type SignalIds = t.TypeOf; @@ -48,23 +42,12 @@ export const signal_status_query = t.object; export const alert_tag_ids = t.array(t.string); export type AlertTagIds = t.TypeOf; -export const fields = t.array(t.string); -export type Fields = t.TypeOf; -export const fieldsOrUndefined = t.union([fields, t.undefined]); -export type FieldsOrUndefined = t.TypeOf; - export const created_at = IsoDateString; export const updated_at = IsoDateString; export const created_by = t.string; export const updated_by = t.string; -export const status_code = PositiveInteger; -export const message = t.string; -export const perPage = PositiveInteger; -export const total = PositiveInteger; export const revision = PositiveInteger; -export const success = t.boolean; -export const success_count = PositiveInteger; export const indexRecord = t.record( t.string, diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/sorting.test.ts b/x-pack/plugins/security_solution/common/api/detection_engine/model/sorting.test.ts index 17ad724039d7e2..04a5fadefe0519 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/model/sorting.test.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/model/sorting.test.ts @@ -5,85 +5,30 @@ * 2.0. */ -import { pipe } from 'fp-ts/lib/pipeable'; -import { left } from 'fp-ts/lib/Either'; -import { foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; -// TODO https://github.com/elastic/security-team/issues/7491 -// eslint-disable-next-line no-restricted-imports -import { DefaultSortOrderAsc, DefaultSortOrderDesc } from './sorting_legacy'; - -describe('Common sorting schemas', () => { - describe('DefaultSortOrderAsc', () => { - describe('Validation succeeds', () => { - it('when valid sort order is passed', () => { - const payload = 'desc'; - const decoded = DefaultSortOrderAsc.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - }); - - describe('Validation fails', () => { - it('when invalid sort order is passed', () => { - const payload = 'behind_you'; - const decoded = DefaultSortOrderAsc.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "behind_you" supplied to "DefaultSortOrderAsc"', - ]); - expect(message.schema).toEqual({}); - }); - }); - - describe('Validation sets the default sort order "asc"', () => { - it('when sort order is not passed', () => { - const payload = undefined; - const decoded = DefaultSortOrderAsc.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual('asc'); - }); - }); +import { expectParseError, expectParseSuccess, stringifyZodError } from '@kbn/zod-helpers'; +import { SortOrder } from './sorting.gen'; + +describe('SortOrder schema', () => { + it('accepts asc value', () => { + const payload = 'asc'; + const result = SortOrder.safeParse(payload); + expectParseSuccess(result); + expect(result.data).toEqual(payload); }); - describe('DefaultSortOrderDesc', () => { - describe('Validation succeeds', () => { - it('when valid sort order is passed', () => { - const payload = 'asc'; - const decoded = DefaultSortOrderDesc.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - }); - - describe('Validation fails', () => { - it('when invalid sort order is passed', () => { - const payload = 'behind_you'; - const decoded = DefaultSortOrderDesc.decode(payload); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "behind_you" supplied to "DefaultSortOrderDesc"', - ]); - expect(message.schema).toEqual({}); - }); - }); - - describe('Validation sets the default sort order "desc"', () => { - it('when sort order is not passed', () => { - const payload = null; - const decoded = DefaultSortOrderDesc.decode(payload); - const message = pipe(decoded, foldLeftRight); + it('accepts desc value', () => { + const payload = 'desc'; + const result = SortOrder.safeParse(payload); + expectParseSuccess(result); + expect(result.data).toEqual(payload); + }); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual('desc'); - }); - }); + it('fails on unknown value', () => { + const payload = 'invalid'; + const result = SortOrder.safeParse(payload); + expectParseError(result); + expect(stringifyZodError(result.error)).toEqual( + "Invalid enum value. Expected 'asc' | 'desc', received 'invalid'" + ); }); }); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/sorting_legacy.ts b/x-pack/plugins/security_solution/common/api/detection_engine/model/sorting_legacy.ts deleted file mode 100644 index 8aa8cf2831ea17..00000000000000 --- a/x-pack/plugins/security_solution/common/api/detection_engine/model/sorting_legacy.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as t from 'io-ts'; -import type { Either } from 'fp-ts/lib/Either'; -import { capitalize } from 'lodash'; - -export type SortOrder = t.TypeOf; -export const SortOrder = t.keyof({ asc: null, desc: null }); - -export type SortOrderOrUndefined = t.TypeOf; -export const SortOrderOrUndefined = t.union([SortOrder, t.undefined]); - -const defaultSortOrder = (order: SortOrder): t.Type => { - return new t.Type( - `DefaultSortOrder${capitalize(order)}`, - SortOrder.is, - (input, context): Either => - input == null ? t.success(order) : SortOrder.validate(input, context), - t.identity - ); -}; - -/** - * Types the DefaultSortOrderAsc as: - * - If undefined, then a default sort order of 'asc' will be set - * - If a string is sent in, then the string will be validated to ensure it's a valid SortOrder - */ -export const DefaultSortOrderAsc = defaultSortOrder('asc'); - -/** - * Types the DefaultSortOrderDesc as: - * - If undefined, then a default sort order of 'desc' will be set - * - If a string is sent in, then the string will be validated to ensure it's a valid SortOrder - */ -export const DefaultSortOrderDesc = defaultSortOrder('desc'); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_exceptions/create_rule_exceptions/create_rule_exceptions_route.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_exceptions/create_rule_exceptions/create_rule_exceptions_route.ts index feecf23faf2939..75eb2b543d337b 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_exceptions/create_rule_exceptions/create_rule_exceptions_route.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_exceptions/create_rule_exceptions/create_rule_exceptions_route.ts @@ -12,9 +12,7 @@ import type { ExceptionListItemSchema, } from '@kbn/securitysolution-io-ts-list-types'; import { createRuleExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; -// TODO https://github.com/elastic/security-team/issues/7491 -// eslint-disable-next-line no-restricted-imports -import { RuleObjectId } from '../../model/rule_schema_legacy'; +import { UUID } from '@kbn/securitysolution-io-ts-types'; /** * URL path parameters of the API route. @@ -22,7 +20,7 @@ import { RuleObjectId } from '../../model/rule_schema_legacy'; export type CreateRuleExceptionsRequestParams = t.TypeOf; export const CreateRuleExceptionsRequestParams = t.exact( t.type({ - id: RuleObjectId, + id: UUID, }) ); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route.gen.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route.gen.ts new file mode 100644 index 00000000000000..d11eea7b167114 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route.gen.ts @@ -0,0 +1,291 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; +import { BooleanFromString } from '@kbn/zod-helpers'; + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + */ + +import { RuleResponse } from '../../model/rule_schema/rule_schemas.gen'; +import { + RuleActionGroup, + RuleActionId, + RuleActionParams, + RuleActionFrequency, + RuleActionAlertsFilter, + IndexPatternArray, + RuleTagArray, + TimelineTemplateId, + TimelineTemplateTitle, +} from '../../model/rule_schema/common_attributes.gen'; + +export type BulkEditSkipReason = z.infer; +export const BulkEditSkipReason = z.literal('RULE_NOT_MODIFIED'); + +export type BulkActionSkipResult = z.infer; +export const BulkActionSkipResult = z.object({ + id: z.string(), + name: z.string().optional(), + skip_reason: BulkEditSkipReason, +}); + +export type RuleDetailsInError = z.infer; +export const RuleDetailsInError = z.object({ + id: z.string(), + name: z.string().optional(), +}); + +export type BulkActionsDryRunErrCode = z.infer; +export const BulkActionsDryRunErrCode = z.enum([ + 'IMMUTABLE', + 'MACHINE_LEARNING_AUTH', + 'MACHINE_LEARNING_INDEX_PATTERN', + 'ESQL_INDEX_PATTERN', +]); +export type BulkActionsDryRunErrCodeEnum = typeof BulkActionsDryRunErrCode.enum; +export const BulkActionsDryRunErrCodeEnum = BulkActionsDryRunErrCode.enum; + +export type NormalizedRuleError = z.infer; +export const NormalizedRuleError = z.object({ + message: z.string(), + status_code: z.number().int(), + err_code: BulkActionsDryRunErrCode.optional(), + rules: z.array(RuleDetailsInError), +}); + +export type BulkEditActionResults = z.infer; +export const BulkEditActionResults = z.object({ + updated: z.array(RuleResponse), + created: z.array(RuleResponse), + deleted: z.array(RuleResponse), + skipped: z.array(BulkActionSkipResult), +}); + +export type BulkEditActionSummary = z.infer; +export const BulkEditActionSummary = z.object({ + failed: z.number().int(), + skipped: z.number().int(), + succeeded: z.number().int(), + total: z.number().int(), +}); + +export type BulkEditActionResponse = z.infer; +export const BulkEditActionResponse = z.object({ + success: z.boolean().optional(), + status_code: z.number().int().optional(), + message: z.string().optional(), + rules_count: z.number().int().optional(), + attributes: z.object({ + results: BulkEditActionResults, + summary: BulkEditActionSummary, + errors: z.array(NormalizedRuleError).optional(), + }), +}); + +export type BulkExportActionResponse = z.infer; +export const BulkExportActionResponse = z.string(); + +export type BulkActionBase = z.infer; +export const BulkActionBase = z.object({ + /** + * Query to filter rules + */ + query: z.string().optional(), + /** + * Array of rule IDs + */ + ids: z.array(z.string()).min(1).optional(), +}); + +export type BulkDeleteRules = z.infer; +export const BulkDeleteRules = BulkActionBase.and( + z.object({ + action: z.literal('delete'), + }) +); + +export type BulkDisableRules = z.infer; +export const BulkDisableRules = BulkActionBase.and( + z.object({ + action: z.literal('disable'), + }) +); + +export type BulkEnableRules = z.infer; +export const BulkEnableRules = BulkActionBase.and( + z.object({ + action: z.literal('enable'), + }) +); + +export type BulkExportRules = z.infer; +export const BulkExportRules = BulkActionBase.and( + z.object({ + action: z.literal('export'), + }) +); + +export type BulkDuplicateRules = z.infer; +export const BulkDuplicateRules = BulkActionBase.and( + z.object({ + action: z.literal('duplicate'), + duplicate: z + .object({ + /** + * Whether to copy exceptions from the original rule + */ + include_exceptions: z.boolean(), + /** + * Whether to copy expired exceptions from the original rule + */ + include_expired_exceptions: z.boolean(), + }) + .optional(), + }) +); + +/** + * The condition for throttling the notification: 'rule', 'no_actions', or time duration + */ +export type ThrottleForBulkActions = z.infer; +export const ThrottleForBulkActions = z.enum(['rule', '1h', '1d', '7d']); +export type ThrottleForBulkActionsEnum = typeof ThrottleForBulkActions.enum; +export const ThrottleForBulkActionsEnum = ThrottleForBulkActions.enum; + +export type BulkActionType = z.infer; +export const BulkActionType = z.enum([ + 'enable', + 'disable', + 'export', + 'delete', + 'duplicate', + 'edit', +]); +export type BulkActionTypeEnum = typeof BulkActionType.enum; +export const BulkActionTypeEnum = BulkActionType.enum; + +export type BulkActionEditType = z.infer; +export const BulkActionEditType = z.enum([ + 'add_tags', + 'delete_tags', + 'set_tags', + 'add_index_patterns', + 'delete_index_patterns', + 'set_index_patterns', + 'set_timeline', + 'add_rule_actions', + 'set_rule_actions', + 'set_schedule', +]); +export type BulkActionEditTypeEnum = typeof BulkActionEditType.enum; +export const BulkActionEditTypeEnum = BulkActionEditType.enum; + +export type NormalizedRuleAction = z.infer; +export const NormalizedRuleAction = z + .object({ + group: RuleActionGroup, + id: RuleActionId, + params: RuleActionParams, + frequency: RuleActionFrequency.optional(), + alerts_filter: RuleActionAlertsFilter.optional(), + }) + .strict(); + +export type BulkActionEditPayloadRuleActions = z.infer; +export const BulkActionEditPayloadRuleActions = z.object({ + type: z.enum(['add_rule_actions', 'set_rule_actions']), + value: z.object({ + throttle: ThrottleForBulkActions.optional(), + actions: z.array(NormalizedRuleAction), + }), +}); + +export type BulkActionEditPayloadSchedule = z.infer; +export const BulkActionEditPayloadSchedule = z.object({ + type: z.literal('set_schedule'), + value: z.object({ + /** + * Interval in which the rule is executed + */ + interval: z.string().regex(/^[1-9]\d*[smh]$/), + /** + * Lookback time for the rule + */ + lookback: z.string().regex(/^[1-9]\d*[smh]$/), + }), +}); + +export type BulkActionEditPayloadIndexPatterns = z.infer; +export const BulkActionEditPayloadIndexPatterns = z.object({ + type: z.enum(['add_index_patterns', 'delete_index_patterns', 'set_index_patterns']), + value: IndexPatternArray, + overwrite_data_views: z.boolean().optional(), +}); + +export type BulkActionEditPayloadTags = z.infer; +export const BulkActionEditPayloadTags = z.object({ + type: z.enum(['add_tags', 'delete_tags', 'set_tags']), + value: RuleTagArray, +}); + +export type BulkActionEditPayloadTimeline = z.infer; +export const BulkActionEditPayloadTimeline = z.object({ + type: z.literal('set_timeline'), + value: z.object({ + timeline_id: TimelineTemplateId, + timeline_title: TimelineTemplateTitle, + }), +}); + +export type BulkActionEditPayload = z.infer; +export const BulkActionEditPayload = z.union([ + BulkActionEditPayloadTags, + BulkActionEditPayloadIndexPatterns, + BulkActionEditPayloadTimeline, + BulkActionEditPayloadRuleActions, + BulkActionEditPayloadSchedule, +]); + +export type BulkEditRules = z.infer; +export const BulkEditRules = BulkActionBase.and( + z.object({ + action: z.literal('edit'), + /** + * Array of objects containing the edit operations + */ + edit: z.array(BulkActionEditPayload).min(1), + }) +); + +export type PerformBulkActionRequestQuery = z.infer; +export const PerformBulkActionRequestQuery = z.object({ + /** + * Enables dry run mode for the request call. + */ + dry_run: BooleanFromString.optional(), +}); +export type PerformBulkActionRequestQueryInput = z.input; + +export type PerformBulkActionRequestBody = z.infer; +export const PerformBulkActionRequestBody = z.union([ + BulkDeleteRules, + BulkDisableRules, + BulkEnableRules, + BulkExportRules, + BulkDuplicateRules, + BulkEditRules, +]); +export type PerformBulkActionRequestBodyInput = z.input; + +export type PerformBulkActionResponse = z.infer; +export const PerformBulkActionResponse = z.union([ + BulkEditActionResponse, + BulkExportActionResponse, +]); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route.mock.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route.mock.ts index 66ce78ff9b6159..fa4fcefbcad1a9 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route.mock.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route.mock.ts @@ -5,18 +5,18 @@ * 2.0. */ -import { BulkActionType, BulkActionEditType } from './bulk_actions_route'; -import type { PerformBulkActionRequestBody } from './bulk_actions_route'; +import type { PerformBulkActionRequestBody } from './bulk_actions_route.gen'; +import { BulkActionEditTypeEnum, BulkActionTypeEnum } from './bulk_actions_route.gen'; export const getPerformBulkActionSchemaMock = (): PerformBulkActionRequestBody => ({ query: '', ids: undefined, - action: BulkActionType.disable, + action: BulkActionTypeEnum.disable, }); export const getPerformBulkActionEditSchemaMock = (): PerformBulkActionRequestBody => ({ query: '', ids: undefined, - action: BulkActionType.edit, - [BulkActionType.edit]: [{ type: BulkActionEditType.add_tags, value: ['tag1'] }], + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [{ type: BulkActionEditTypeEnum.add_tags, value: ['tag1'] }], }); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route.schema.yaml b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route.schema.yaml index 8eba09881bbd99..583782f086ae79 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route.schema.yaml @@ -6,7 +6,7 @@ paths: /api/detection_engine/rules/_bulk_action: post: operationId: PerformBulkAction - x-codegen-enabled: false + x-codegen-enabled: true summary: Applies a bulk action to multiple rules description: The bulk action is applied to all rules that match the filter or to the list of rules by their IDs. tags: @@ -22,19 +22,24 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/PerformBulkActionRequest' + oneOf: + - $ref: '#/components/schemas/BulkDeleteRules' + - $ref: '#/components/schemas/BulkDisableRules' + - $ref: '#/components/schemas/BulkEnableRules' + - $ref: '#/components/schemas/BulkExportRules' + - $ref: '#/components/schemas/BulkDuplicateRules' + - $ref: '#/components/schemas/BulkEditRules' responses: 200: description: OK content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/BulkEditActionResponse' + oneOf: + - $ref: '#/components/schemas/BulkEditActionResponse' + - $ref: '#/components/schemas/BulkExportActionResponse' components: - x-codegen-enabled: false schemas: BulkEditSkipReason: type: string @@ -66,6 +71,11 @@ components: BulkActionsDryRunErrCode: type: string + enum: + - IMMUTABLE + - MACHINE_LEARNING_AUTH + - MACHINE_LEARNING_INDEX_PATTERN + - ESQL_INDEX_PATTERN NormalizedRuleError: type: object @@ -127,35 +137,17 @@ components: - succeeded - total - BulkEditActionSuccessResponse: + BulkEditActionResponse: type: object properties: success: type: boolean - rules_count: - type: integer - attributes: - type: object - properties: - results: - $ref: '#/components/schemas/BulkEditActionResults' - summary: - $ref: '#/components/schemas/BulkEditActionSummary' - required: - - results - - summary - required: - - success - - rules_count - - attributes - - BulkEditActionErrorResponse: - type: object - properties: status_code: type: integer message: type: string + rules_count: + type: integer attributes: type: object properties: @@ -171,35 +163,23 @@ components: - results - summary required: - - status_code - - message - attributes - BulkEditActionResponse: - oneOf: - - $ref: '#/components/schemas/BulkEditActionSuccessResponse' - - $ref: '#/components/schemas/BulkEditActionErrorResponse' + BulkExportActionResponse: + type: string BulkActionBase: - oneOf: - - type: object - properties: - query: - type: string - description: Query to filter rules - required: - - query - additionalProperties: false - - - type: object - properties: - ids: - type: array - description: Array of rule IDs - minItems: 1 - items: - type: string - additionalProperties: false + type: object + properties: + query: + type: string + description: Query to filter rules + ids: + type: array + description: Array of rule IDs + minItems: 1 + items: + type: string BulkDeleteRules: allOf: @@ -262,35 +242,20 @@ components: include_expired_exceptions: type: boolean description: Whether to copy expired exceptions from the original rule + required: + - include_exceptions + - include_expired_exceptions required: - action - RuleActionSummary: - type: boolean - description: Action summary indicates whether we will send a summary notification about all the generate alerts or notification per individual alert - - RuleActionNotifyWhen: - type: string - description: "The condition for throttling the notification: 'onActionGroupChange', 'onActiveAlert', or 'onThrottleInterval'" - enum: - - onActionGroupChange - - onActiveAlert - - onThrottleInterval - - RuleActionThrottle: + ThrottleForBulkActions: type: string description: "The condition for throttling the notification: 'rule', 'no_actions', or time duration" - - RuleActionFrequency: - type: object - properties: - summary: - $ref: '#/components/schemas/RuleActionSummary' - notifyWhen: - $ref: '#/components/schemas/RuleActionNotifyWhen' - throttle: - $ref: '#/components/schemas/RuleActionThrottle' - nullable: true + enum: + - rule + - 1h + - 1d + - 7d BulkActionType: type: string @@ -316,6 +281,26 @@ components: - set_rule_actions - set_schedule + # Per rulesClient.bulkEdit rules actions operation contract (x-pack/plugins/alerting/server/rules_client/rules_client.ts) normalized rule action object is expected (NormalizedAlertAction) as value for the edit operation + NormalizedRuleAction: + type: object + properties: + group: + $ref: '../../model/rule_schema/common_attributes.schema.yaml#/components/schemas/RuleActionGroup' + id: + $ref: '../../model/rule_schema/common_attributes.schema.yaml#/components/schemas/RuleActionId' + params: + $ref: '../../model/rule_schema/common_attributes.schema.yaml#/components/schemas/RuleActionParams' + frequency: + $ref: '../../model/rule_schema/common_attributes.schema.yaml#/components/schemas/RuleActionFrequency' + alerts_filter: + $ref: '../../model/rule_schema/common_attributes.schema.yaml#/components/schemas/RuleActionAlertsFilter' + required: + - group + - id + - params + additionalProperties: false + BulkActionEditPayloadRuleActions: type: object properties: @@ -326,28 +311,11 @@ components: type: object properties: throttle: - $ref: '#/components/schemas/RuleActionThrottle' + $ref: '#/components/schemas/ThrottleForBulkActions' actions: type: array items: - type: object - properties: - group: - type: string - description: Action group - id: - type: string - description: Action ID - params: - type: object - description: Action parameters - frequency: - $ref: '#/components/schemas/RuleActionFrequency' - description: Action frequency - required: - - group - - id - - params + $ref: '#/components/schemas/NormalizedRuleAction' required: - actions required: @@ -366,12 +334,19 @@ components: interval: type: string description: Interval in which the rule is executed + pattern: '^[1-9]\d*[smh]$' # any number except zero followed by one of the suffixes 's', 'm', 'h' + example: '1h' lookback: type: string description: Lookback time for the rule + pattern: '^[1-9]\d*[smh]$' # any number except zero followed by one of the suffixes 's', 'm', 'h' + example: '1h' required: - interval - lookback + required: + - type + - value BulkActionEditPayloadIndexPatterns: type: object @@ -441,22 +416,13 @@ components: properties: action: type: string - x-type: literal enum: [edit] edit: type: array description: Array of objects containing the edit operations items: $ref: '#/components/schemas/BulkActionEditPayload' + minItems: 1 required: - action - - rule - - PerformBulkActionRequest: - oneOf: - - $ref: '#/components/schemas/BulkDeleteRules' - - $ref: '#/components/schemas/BulkDisableRules' - - $ref: '#/components/schemas/BulkEnableRules' - - $ref: '#/components/schemas/BulkExportRules' - - $ref: '#/components/schemas/BulkDuplicateRules' - - $ref: '#/components/schemas/BulkEditRules' + - edit diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route.test.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route.test.ts index 70ae5486743329..ff5289f79d98d3 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route.test.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route.test.ts @@ -5,19 +5,12 @@ * 2.0. */ -import { left } from 'fp-ts/lib/Either'; -import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; +import { expectParseError, expectParseSuccess, stringifyZodError } from '@kbn/zod-helpers'; import { + BulkActionEditTypeEnum, + BulkActionTypeEnum, PerformBulkActionRequestBody, - BulkActionType, - BulkActionEditType, -} from './bulk_actions_route'; - -const retrieveValidationMessage = (payload: unknown) => { - const decoded = PerformBulkActionRequestBody.decode(payload); - const checked = exactCheck(payload, decoded); - return foldLeftRight(checked); -}; +} from './bulk_actions_route.gen'; describe('Perform bulk action request schema', () => { describe('cases common to every bulk action', () => { @@ -25,62 +18,64 @@ describe('Perform bulk action request schema', () => { test('valid request: missing query', () => { const payload: PerformBulkActionRequestBody = { query: undefined, - action: BulkActionType.enable, + action: BulkActionTypeEnum.enable, }; - const message = retrieveValidationMessage(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); + expectParseSuccess(result); + expect(result.data).toEqual(payload); }); test('invalid request: missing action', () => { const payload: Omit = { query: 'name: test', }; - const message = retrieveValidationMessage(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); + expectParseError(result); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "action"', - 'Invalid value "undefined" supplied to "edit"', - ]); - expect(message.schema).toEqual({}); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"action: Invalid literal value, expected \\"delete\\", action: Invalid literal value, expected \\"disable\\", action: Invalid literal value, expected \\"enable\\", action: Invalid literal value, expected \\"export\\", action: Invalid literal value, expected \\"duplicate\\", and 2 more"` + ); }); test('invalid request: unknown action', () => { const payload: Omit & { action: 'unknown' } = { - query: 'name: test', action: 'unknown', }; - const message = retrieveValidationMessage(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); + expectParseError(result); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "unknown" supplied to "action"', - 'Invalid value "undefined" supplied to "edit"', - ]); - expect(message.schema).toEqual({}); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"action: Invalid literal value, expected \\"delete\\", action: Invalid literal value, expected \\"disable\\", action: Invalid literal value, expected \\"enable\\", action: Invalid literal value, expected \\"export\\", action: Invalid literal value, expected \\"duplicate\\", and 2 more"` + ); }); - test('invalid request: unknown property', () => { + test('strips unknown properties', () => { const payload = { query: 'name: test', - action: BulkActionType.enable, + action: BulkActionTypeEnum.enable, mock: ['id'], }; - const message = retrieveValidationMessage(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); + expectParseSuccess(result); - expect(getPaths(left(message.errors))).toEqual(['invalid keys "mock,["id"]"']); - expect(message.schema).toEqual({}); + expect(result.data).toEqual({ + query: 'name: test', + action: BulkActionTypeEnum.enable, + }); }); test('invalid request: wrong type for ids', () => { const payload = { ids: 'mock', - action: BulkActionType.enable, + action: BulkActionTypeEnum.enable, }; - const message = retrieveValidationMessage(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); + expectParseError(result); - expect(getPaths(left(message.errors))).toEqual(['Invalid value "mock" supplied to "ids"']); - expect(message.schema).toEqual({}); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"ids: Expected array, received string, action: Invalid literal value, expected \\"delete\\", ids: Expected array, received string, action: Invalid literal value, expected \\"disable\\", ids: Expected array, received string, and 7 more"` + ); }); }); @@ -88,11 +83,11 @@ describe('Perform bulk action request schema', () => { test('valid request', () => { const payload: PerformBulkActionRequestBody = { query: 'name: test', - action: BulkActionType.enable, + action: BulkActionTypeEnum.enable, }; - const message = retrieveValidationMessage(payload); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); + expectParseSuccess(result); + expect(result.data).toEqual(payload); }); }); @@ -100,11 +95,11 @@ describe('Perform bulk action request schema', () => { test('valid request', () => { const payload: PerformBulkActionRequestBody = { query: 'name: test', - action: BulkActionType.disable, + action: BulkActionTypeEnum.disable, }; - const message = retrieveValidationMessage(payload); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); + expectParseSuccess(result); + expect(result.data).toEqual(payload); }); }); @@ -112,11 +107,11 @@ describe('Perform bulk action request schema', () => { test('valid request', () => { const payload: PerformBulkActionRequestBody = { query: 'name: test', - action: BulkActionType.export, + action: BulkActionTypeEnum.export, }; - const message = retrieveValidationMessage(payload); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); + expectParseSuccess(result); + expect(result.data).toEqual(payload); }); }); @@ -124,11 +119,11 @@ describe('Perform bulk action request schema', () => { test('valid request', () => { const payload: PerformBulkActionRequestBody = { query: 'name: test', - action: BulkActionType.delete, + action: BulkActionTypeEnum.delete, }; - const message = retrieveValidationMessage(payload); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); + expectParseSuccess(result); + expect(result.data).toEqual(payload); }); }); @@ -136,15 +131,15 @@ describe('Perform bulk action request schema', () => { test('valid request', () => { const payload: PerformBulkActionRequestBody = { query: 'name: test', - action: BulkActionType.duplicate, - [BulkActionType.duplicate]: { + action: BulkActionTypeEnum.duplicate, + [BulkActionTypeEnum.duplicate]: { include_exceptions: false, include_expired_exceptions: false, }, }; - const message = retrieveValidationMessage(payload); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); + expectParseSuccess(result); + expect(result.data).toEqual(payload); }); }); @@ -153,47 +148,30 @@ describe('Perform bulk action request schema', () => { test('invalid request: missing edit payload', () => { const payload = { query: 'name: test', - action: BulkActionType.edit, - }; - - const message = retrieveValidationMessage(payload); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "edit" supplied to "action"', - 'Invalid value "undefined" supplied to "edit"', - ]); - expect(message.schema).toEqual({}); - }); - - test('invalid request: specified edit payload for another action', () => { - const payload = { - query: 'name: test', - action: BulkActionType.enable, - [BulkActionType.edit]: [{ type: BulkActionEditType.set_tags, value: ['test-tag'] }], + action: BulkActionTypeEnum.edit, }; - const message = retrieveValidationMessage(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); + expectParseError(result); - expect(getPaths(left(message.errors))).toEqual([ - 'invalid keys "edit,[{"type":"set_tags","value":["test-tag"]}]"', - ]); - expect(message.schema).toEqual({}); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"action: Invalid literal value, expected \\"delete\\", action: Invalid literal value, expected \\"disable\\", action: Invalid literal value, expected \\"enable\\", action: Invalid literal value, expected \\"export\\", action: Invalid literal value, expected \\"duplicate\\", and 1 more"` + ); }); test('invalid request: wrong type for edit payload', () => { const payload = { query: 'name: test', - action: BulkActionType.edit, - [BulkActionType.edit]: { type: BulkActionEditType.set_tags, value: ['test-tag'] }, + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: { type: BulkActionEditTypeEnum.set_tags, value: ['test-tag'] }, }; - const message = retrieveValidationMessage(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); + expectParseError(result); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "edit" supplied to "action"', - 'Invalid value "{"type":"set_tags","value":["test-tag"]}" supplied to "edit"', - ]); - expect(message.schema).toEqual({}); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"action: Invalid literal value, expected \\"delete\\", action: Invalid literal value, expected \\"disable\\", action: Invalid literal value, expected \\"enable\\", action: Invalid literal value, expected \\"export\\", action: Invalid literal value, expected \\"duplicate\\", and 1 more"` + ); }); }); @@ -201,57 +179,61 @@ describe('Perform bulk action request schema', () => { test('invalid request: wrong tags type', () => { const payload = { query: 'name: test', - action: BulkActionType.edit, - [BulkActionType.edit]: [{ type: BulkActionEditType.set_tags, value: 'test-tag' }], + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [{ type: BulkActionEditTypeEnum.set_tags, value: 'test-tag' }], }; - const message = retrieveValidationMessage(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); + expectParseError(result); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "edit" supplied to "action"', - 'Invalid value "test-tag" supplied to "edit,value"', - 'Invalid value "set_tags" supplied to "edit,type"', - ]); - expect(message.schema).toEqual({}); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"action: Invalid literal value, expected \\"delete\\", action: Invalid literal value, expected \\"disable\\", action: Invalid literal value, expected \\"enable\\", action: Invalid literal value, expected \\"export\\", action: Invalid literal value, expected \\"duplicate\\", and 9 more"` + ); }); test('valid request: add_tags edit action', () => { const payload = { query: 'name: test', - action: BulkActionType.edit, - [BulkActionType.edit]: [{ type: BulkActionEditType.add_tags, value: ['test-tag'] }], + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ + { type: BulkActionEditTypeEnum.add_tags, value: ['test-tag'] }, + ], }; - const message = retrieveValidationMessage(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); + expectParseSuccess(result); + expect(result.data).toEqual(payload); }); test('valid request: set_tags edit action', () => { const payload = { query: 'name: test', - action: BulkActionType.edit, - [BulkActionType.edit]: [{ type: BulkActionEditType.set_tags, value: ['test-tag'] }], + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ + { type: BulkActionEditTypeEnum.set_tags, value: ['test-tag'] }, + ], }; - const message = retrieveValidationMessage(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); + expectParseSuccess(result); + expect(result.data).toEqual(payload); }); test('valid request: delete_tags edit action', () => { const payload = { query: 'name: test', - action: BulkActionType.edit, - [BulkActionType.edit]: [{ type: BulkActionEditType.delete_tags, value: ['test-tag'] }], + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ + { type: BulkActionEditTypeEnum.delete_tags, value: ['test-tag'] }, + ], }; - const message = retrieveValidationMessage(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); + expectParseSuccess(result); + expect(result.data).toEqual(payload); }); }); @@ -259,63 +241,61 @@ describe('Perform bulk action request schema', () => { test('invalid request: wrong index_patterns type', () => { const payload = { query: 'name: test', - action: BulkActionType.edit, - [BulkActionType.edit]: [{ type: BulkActionEditType.set_tags, value: 'logs-*' }], + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [{ type: BulkActionEditTypeEnum.set_tags, value: 'logs-*' }], }; - const message = retrieveValidationMessage(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); + expectParseError(result); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "edit" supplied to "action"', - 'Invalid value "logs-*" supplied to "edit,value"', - 'Invalid value "set_tags" supplied to "edit,type"', - ]); - expect(message.schema).toEqual({}); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"action: Invalid literal value, expected \\"delete\\", action: Invalid literal value, expected \\"disable\\", action: Invalid literal value, expected \\"enable\\", action: Invalid literal value, expected \\"export\\", action: Invalid literal value, expected \\"duplicate\\", and 9 more"` + ); }); test('valid request: set_index_patterns edit action', () => { const payload: PerformBulkActionRequestBody = { query: 'name: test', - action: BulkActionType.edit, - [BulkActionType.edit]: [ - { type: BulkActionEditType.set_index_patterns, value: ['logs-*'] }, + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ + { type: BulkActionEditTypeEnum.set_index_patterns, value: ['logs-*'] }, ], }; - const message = retrieveValidationMessage(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); + expectParseSuccess(result); + expect(result.data).toEqual(payload); }); test('valid request: add_index_patterns edit action', () => { const payload: PerformBulkActionRequestBody = { query: 'name: test', - action: BulkActionType.edit, - [BulkActionType.edit]: [ - { type: BulkActionEditType.add_index_patterns, value: ['logs-*'] }, + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ + { type: BulkActionEditTypeEnum.add_index_patterns, value: ['logs-*'] }, ], }; - const message = retrieveValidationMessage(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); + expectParseSuccess(result); + expect(result.data).toEqual(payload); }); test('valid request: delete_index_patterns edit action', () => { const payload: PerformBulkActionRequestBody = { query: 'name: test', - action: BulkActionType.edit, - [BulkActionType.edit]: [ - { type: BulkActionEditType.delete_index_patterns, value: ['logs-*'] }, + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ + { type: BulkActionEditTypeEnum.delete_index_patterns, value: ['logs-*'] }, ], }; - const message = retrieveValidationMessage(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); + expectParseSuccess(result); + expect(result.data).toEqual(payload); }); }); @@ -323,27 +303,25 @@ describe('Perform bulk action request schema', () => { test('invalid request: wrong timeline payload type', () => { const payload = { query: 'name: test', - action: BulkActionType.edit, - [BulkActionType.edit]: [{ type: BulkActionEditType.set_timeline, value: [] }], + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [{ type: BulkActionEditTypeEnum.set_timeline, value: [] }], }; - const message = retrieveValidationMessage(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "edit" supplied to "action"', - 'Invalid value "set_timeline" supplied to "edit,type"', - 'Invalid value "[]" supplied to "edit,value"', - ]); - expect(message.schema).toEqual({}); + expectParseError(result); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"action: Invalid literal value, expected \\"delete\\", action: Invalid literal value, expected \\"disable\\", action: Invalid literal value, expected \\"enable\\", action: Invalid literal value, expected \\"export\\", action: Invalid literal value, expected \\"duplicate\\", and 7 more"` + ); }); test('invalid request: missing timeline_id', () => { const payload = { query: 'name: test', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_timeline, + type: BulkActionEditTypeEnum.set_timeline, value: { timeline_title: 'Test timeline title', }, @@ -351,24 +329,21 @@ describe('Perform bulk action request schema', () => { ], }; - const message = retrieveValidationMessage(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); - expect(getPaths(left(message.errors))).toEqual( - expect.arrayContaining([ - 'Invalid value "{"timeline_title":"Test timeline title"}" supplied to "edit,value"', - 'Invalid value "undefined" supplied to "edit,value,timeline_id"', - ]) + expectParseError(result); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"action: Invalid literal value, expected \\"delete\\", action: Invalid literal value, expected \\"disable\\", action: Invalid literal value, expected \\"enable\\", action: Invalid literal value, expected \\"export\\", action: Invalid literal value, expected \\"duplicate\\", and 10 more"` ); - expect(message.schema).toEqual({}); }); test('valid request: set_timeline edit action', () => { const payload: PerformBulkActionRequestBody = { query: 'name: test', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_timeline, + type: BulkActionEditTypeEnum.set_timeline, value: { timeline_id: 'timelineid', timeline_title: 'Test timeline title', @@ -377,10 +352,10 @@ describe('Perform bulk action request schema', () => { ], }; - const message = retrieveValidationMessage(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); + expectParseSuccess(result); + expect(result.data).toEqual(payload); }); }); @@ -388,27 +363,25 @@ describe('Perform bulk action request schema', () => { test('invalid request: wrong schedules payload type', () => { const payload = { query: 'name: test', - action: BulkActionType.edit, - [BulkActionType.edit]: [{ type: BulkActionEditType.set_schedule, value: [] }], + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [{ type: BulkActionEditTypeEnum.set_schedule, value: [] }], }; - const message = retrieveValidationMessage(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "edit" supplied to "action"', - 'Invalid value "set_schedule" supplied to "edit,type"', - 'Invalid value "[]" supplied to "edit,value"', - ]); - expect(message.schema).toEqual({}); + expectParseError(result); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"action: Invalid literal value, expected \\"delete\\", action: Invalid literal value, expected \\"disable\\", action: Invalid literal value, expected \\"enable\\", action: Invalid literal value, expected \\"export\\", action: Invalid literal value, expected \\"duplicate\\", and 7 more"` + ); }); test('invalid request: wrong type of payload data', () => { const payload = { query: 'name: test', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_schedule, + type: BulkActionEditTypeEnum.set_schedule, value: { interval: '-10m', lookback: '1m', @@ -417,25 +390,21 @@ describe('Perform bulk action request schema', () => { ], } as PerformBulkActionRequestBody; - const message = retrieveValidationMessage(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); - expect(getPaths(left(message.errors))).toEqual( - expect.arrayContaining([ - 'Invalid value "edit" supplied to "action"', - 'Invalid value "{"interval":"-10m","lookback":"1m"}" supplied to "edit,value"', - 'Invalid value "-10m" supplied to "edit,value,interval"', - ]) + expectParseError(result); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"edit.0.value.interval: Invalid"` ); - expect(message.schema).toEqual({}); }); test('invalid request: missing interval', () => { const payload = { query: 'name: test', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_schedule, + type: BulkActionEditTypeEnum.set_schedule, value: { lookback: '1m', }, @@ -443,25 +412,21 @@ describe('Perform bulk action request schema', () => { ], } as PerformBulkActionRequestBody; - const message = retrieveValidationMessage(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); - expect(getPaths(left(message.errors))).toEqual( - expect.arrayContaining([ - 'Invalid value "edit" supplied to "action"', - 'Invalid value "{"lookback":"1m"}" supplied to "edit,value"', - 'Invalid value "undefined" supplied to "edit,value,interval"', - ]) + expectParseError(result); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"action: Invalid literal value, expected \\"delete\\", action: Invalid literal value, expected \\"disable\\", action: Invalid literal value, expected \\"enable\\", action: Invalid literal value, expected \\"export\\", action: Invalid literal value, expected \\"duplicate\\", and 10 more"` ); - expect(message.schema).toEqual({}); }); test('invalid request: missing lookback', () => { const payload = { query: 'name: test', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_schedule, + type: BulkActionEditTypeEnum.set_schedule, value: { interval: '1m', }, @@ -469,25 +434,21 @@ describe('Perform bulk action request schema', () => { ], } as PerformBulkActionRequestBody; - const message = retrieveValidationMessage(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); - expect(getPaths(left(message.errors))).toEqual( - expect.arrayContaining([ - 'Invalid value "edit" supplied to "action"', - 'Invalid value "{"interval":"1m"}" supplied to "edit,value"', - 'Invalid value "undefined" supplied to "edit,value,lookback"', - ]) + expectParseError(result); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"action: Invalid literal value, expected \\"delete\\", action: Invalid literal value, expected \\"disable\\", action: Invalid literal value, expected \\"enable\\", action: Invalid literal value, expected \\"export\\", action: Invalid literal value, expected \\"duplicate\\", and 10 more"` ); - expect(message.schema).toEqual({}); }); test('valid request: set_schedule edit action', () => { const payload: PerformBulkActionRequestBody = { query: 'name: test', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_schedule, + type: BulkActionEditTypeEnum.set_schedule, value: { interval: '1m', lookback: '1m', @@ -496,10 +457,10 @@ describe('Perform bulk action request schema', () => { ], } as PerformBulkActionRequestBody; - const message = retrieveValidationMessage(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); + expectParseSuccess(result); + expect(result.data).toEqual(payload); }); }); @@ -507,25 +468,25 @@ describe('Perform bulk action request schema', () => { test('invalid request: invalid rule actions payload', () => { const payload = { query: 'name: test', - action: BulkActionType.edit, - [BulkActionType.edit]: [{ type: BulkActionEditType.add_rule_actions, value: [] }], + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [{ type: BulkActionEditTypeEnum.add_rule_actions, value: [] }], }; - const message = retrieveValidationMessage(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); - expect(getPaths(left(message.errors))).toEqual( - expect.arrayContaining(['Invalid value "[]" supplied to "edit,value"']) + expectParseError(result); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"action: Invalid literal value, expected \\"delete\\", action: Invalid literal value, expected \\"disable\\", action: Invalid literal value, expected \\"enable\\", action: Invalid literal value, expected \\"export\\", action: Invalid literal value, expected \\"duplicate\\", and 7 more"` ); - expect(message.schema).toEqual({}); }); test('invalid request: missing actions in payload', () => { const payload = { query: 'name: test', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.add_rule_actions, + type: BulkActionEditTypeEnum.add_rule_actions, value: { throttle: '1h', }, @@ -533,21 +494,21 @@ describe('Perform bulk action request schema', () => { ], }; - const message = retrieveValidationMessage(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); - expect(getPaths(left(message.errors))).toEqual( - expect.arrayContaining(['Invalid value "undefined" supplied to "edit,value,actions"']) + expectParseError(result); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"action: Invalid literal value, expected \\"delete\\", action: Invalid literal value, expected \\"disable\\", action: Invalid literal value, expected \\"enable\\", action: Invalid literal value, expected \\"export\\", action: Invalid literal value, expected \\"duplicate\\", and 11 more"` ); - expect(message.schema).toEqual({}); }); test('invalid request: invalid action_type_id property in actions array', () => { const payload = { query: 'name: test', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.add_rule_actions, + type: BulkActionEditTypeEnum.add_rule_actions, value: { throttle: '1h', actions: [ @@ -567,20 +528,20 @@ describe('Perform bulk action request schema', () => { ], }; - const message = retrieveValidationMessage(payload); - expect(getPaths(left(message.errors))).toEqual( - expect.arrayContaining(['invalid keys "action_type_id"']) + const result = PerformBulkActionRequestBody.safeParse(payload); + expectParseError(result); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"edit.0.value.actions.0: Unrecognized key(s) in object: 'action_type_id'"` ); - expect(message.schema).toEqual({}); }); test('valid request: add_rule_actions edit action', () => { const payload: PerformBulkActionRequestBody = { query: 'name: test', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.add_rule_actions, + type: BulkActionEditTypeEnum.add_rule_actions, value: { throttle: '1h', actions: [ @@ -599,19 +560,19 @@ describe('Perform bulk action request schema', () => { ], }; - const message = retrieveValidationMessage(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); + expectParseSuccess(result); + expect(result.data).toEqual(payload); }); test('valid request: set_rule_actions edit action', () => { const payload: PerformBulkActionRequestBody = { query: 'name: test', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_rule_actions, + type: BulkActionEditTypeEnum.set_rule_actions, value: { throttle: '1h', actions: [ @@ -632,10 +593,10 @@ describe('Perform bulk action request schema', () => { ], }; - const message = retrieveValidationMessage(payload); + const result = PerformBulkActionRequestBody.safeParse(payload); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); + expectParseSuccess(result); + expect(result.data).toEqual(payload); }); }); }); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route.ts deleted file mode 100644 index 768626d08769dd..00000000000000 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route.ts +++ /dev/null @@ -1,269 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as t from 'io-ts'; - -import { NonEmptyArray, TimeDuration } from '@kbn/securitysolution-io-ts-types'; -import { - RuleActionAlertsFilter, - RuleActionFrequency, - RuleActionGroup, - RuleActionId, - RuleActionParams, -} from '@kbn/securitysolution-io-ts-alerting-types'; - -import type { BulkActionSkipResult } from '@kbn/alerting-plugin/common'; -import type { RuleResponse } from '../../model'; -import type { BulkActionsDryRunErrCode } from '../../../../constants'; - -// TODO https://github.com/elastic/security-team/issues/7491 -// eslint-disable-next-line no-restricted-imports -import { - IndexPatternArray, - RuleQuery, - RuleTagArray, - TimelineTemplateId, - TimelineTemplateTitle, -} from '../../model/rule_schema_legacy'; - -export enum BulkActionType { - 'enable' = 'enable', - 'disable' = 'disable', - 'export' = 'export', - 'delete' = 'delete', - 'duplicate' = 'duplicate', - 'edit' = 'edit', -} - -export enum BulkActionEditType { - 'add_tags' = 'add_tags', - 'delete_tags' = 'delete_tags', - 'set_tags' = 'set_tags', - 'add_index_patterns' = 'add_index_patterns', - 'delete_index_patterns' = 'delete_index_patterns', - 'set_index_patterns' = 'set_index_patterns', - 'set_timeline' = 'set_timeline', - 'add_rule_actions' = 'add_rule_actions', - 'set_rule_actions' = 'set_rule_actions', - 'set_schedule' = 'set_schedule', -} - -export type ThrottleForBulkActions = t.TypeOf; -export const ThrottleForBulkActions = t.union([ - t.literal('rule'), - t.literal('1h'), - t.literal('1d'), - t.literal('7d'), -]); - -type BulkActionEditPayloadTags = t.TypeOf; -const BulkActionEditPayloadTags = t.type({ - type: t.union([ - t.literal(BulkActionEditType.add_tags), - t.literal(BulkActionEditType.delete_tags), - t.literal(BulkActionEditType.set_tags), - ]), - value: RuleTagArray, -}); - -export type BulkActionEditPayloadIndexPatterns = t.TypeOf< - typeof BulkActionEditPayloadIndexPatterns ->; -const BulkActionEditPayloadIndexPatterns = t.intersection([ - t.type({ - type: t.union([ - t.literal(BulkActionEditType.add_index_patterns), - t.literal(BulkActionEditType.delete_index_patterns), - t.literal(BulkActionEditType.set_index_patterns), - ]), - value: IndexPatternArray, - }), - t.exact(t.partial({ overwrite_data_views: t.boolean })), -]); - -type BulkActionEditPayloadTimeline = t.TypeOf; -const BulkActionEditPayloadTimeline = t.type({ - type: t.literal(BulkActionEditType.set_timeline), - value: t.type({ - timeline_id: TimelineTemplateId, - timeline_title: TimelineTemplateTitle, - }), -}); - -/** - * per rulesClient.bulkEdit rules actions operation contract (x-pack/plugins/alerting/server/rules_client/rules_client.ts) - * normalized rule action object is expected (NormalizedAlertAction) as value for the edit operation - */ -export type NormalizedRuleAction = t.TypeOf; -export const NormalizedRuleAction = t.exact( - t.intersection([ - t.type({ - group: RuleActionGroup, - id: RuleActionId, - params: RuleActionParams, - }), - t.partial({ frequency: RuleActionFrequency }), - t.partial({ alerts_filter: RuleActionAlertsFilter }), - ]) -); - -export type BulkActionEditPayloadRuleActions = t.TypeOf; -export const BulkActionEditPayloadRuleActions = t.type({ - type: t.union([ - t.literal(BulkActionEditType.add_rule_actions), - t.literal(BulkActionEditType.set_rule_actions), - ]), - value: t.intersection([ - t.partial({ throttle: ThrottleForBulkActions }), - t.type({ - actions: t.array(NormalizedRuleAction), - }), - ]), -}); - -type BulkActionEditPayloadSchedule = t.TypeOf; -const BulkActionEditPayloadSchedule = t.type({ - type: t.literal(BulkActionEditType.set_schedule), - value: t.type({ - interval: TimeDuration({ allowedUnits: ['s', 'm', 'h'] }), - lookback: TimeDuration({ allowedUnits: ['s', 'm', 'h'] }), - }), -}); - -export type BulkActionEditPayload = t.TypeOf; -export const BulkActionEditPayload = t.union([ - BulkActionEditPayloadTags, - BulkActionEditPayloadIndexPatterns, - BulkActionEditPayloadTimeline, - BulkActionEditPayloadRuleActions, - BulkActionEditPayloadSchedule, -]); - -const bulkActionDuplicatePayload = t.exact( - t.type({ - include_exceptions: t.boolean, - include_expired_exceptions: t.boolean, - }) -); - -export type BulkActionDuplicatePayload = t.TypeOf; - -/** - * actions that modify rules attributes - */ -export type BulkActionEditForRuleAttributes = - | BulkActionEditPayloadTags - | BulkActionEditPayloadRuleActions - | BulkActionEditPayloadSchedule; - -/** - * actions that modify rules params - */ -export type BulkActionEditForRuleParams = - | BulkActionEditPayloadIndexPatterns - | BulkActionEditPayloadTimeline - | BulkActionEditPayloadSchedule; - -/** - * Request body parameters of the API route. - */ -export type PerformBulkActionRequestBody = t.TypeOf; -export const PerformBulkActionRequestBody = t.intersection([ - t.exact( - t.type({ - query: t.union([RuleQuery, t.undefined]), - }) - ), - t.exact(t.partial({ ids: NonEmptyArray(t.string) })), - t.union([ - t.exact( - t.type({ - action: t.union([ - t.literal(BulkActionType.delete), - t.literal(BulkActionType.disable), - t.literal(BulkActionType.enable), - t.literal(BulkActionType.export), - ]), - }) - ), - t.intersection([ - t.exact( - t.type({ - action: t.literal(BulkActionType.duplicate), - }) - ), - t.exact( - t.partial({ - [BulkActionType.duplicate]: bulkActionDuplicatePayload, - }) - ), - ]), - t.exact( - t.type({ - action: t.literal(BulkActionType.edit), - [BulkActionType.edit]: NonEmptyArray(BulkActionEditPayload), - }) - ), - ]), -]); - -/** - * Query string parameters of the API route. - */ -export type PerformBulkActionRequestQuery = t.TypeOf; -export const PerformBulkActionRequestQuery = t.exact( - t.partial({ - dry_run: t.union([t.literal('true'), t.literal('false')]), - }) -); - -export interface RuleDetailsInError { - id: string; - name?: string; -} -export interface NormalizedRuleError { - message: string; - status_code: number; - err_code?: BulkActionsDryRunErrCode; - rules: RuleDetailsInError[]; -} -export interface BulkEditActionResults { - updated: RuleResponse[]; - created: RuleResponse[]; - deleted: RuleResponse[]; - skipped: BulkActionSkipResult[]; -} - -export interface BulkEditActionSummary { - failed: number; - skipped: number; - succeeded: number; - total: number; -} -export interface BulkEditActionSuccessResponse { - success: boolean; - rules_count: number; - attributes: { - results: BulkEditActionResults; - summary: BulkEditActionSummary; - }; -} -export interface BulkEditActionErrorResponse { - status_code: number; - message: string; - attributes: { - results: BulkEditActionResults; - summary: BulkEditActionSummary; - errors?: NormalizedRuleError[]; - }; -} - -export type BulkEditActionResponse = BulkEditActionSuccessResponse | BulkEditActionErrorResponse; - -export type BulkExportActionResponse = string; - -export type PerformBulkActionResponse = BulkEditActionResponse | BulkExportActionResponse; diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_actions/bulk_actions_types.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_actions/bulk_actions_types.ts new file mode 100644 index 00000000000000..6e57e5abe24103 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_actions/bulk_actions_types.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { + BulkActionEditPayloadIndexPatterns, + BulkActionEditPayloadRuleActions, + BulkActionEditPayloadSchedule, + BulkActionEditPayloadTags, + BulkActionEditPayloadTimeline, +} from './bulk_actions_route.gen'; + +/** + * actions that modify rules attributes + */ +export type BulkActionEditForRuleAttributes = + | BulkActionEditPayloadTags + | BulkActionEditPayloadRuleActions + | BulkActionEditPayloadSchedule; + +/** + * actions that modify rules params + */ +export type BulkActionEditForRuleParams = + | BulkActionEditPayloadIndexPatterns + | BulkActionEditPayloadTimeline + | BulkActionEditPayloadSchedule; diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_crud/bulk_create_rules/bulk_create_rules_route.test.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_crud/bulk_create_rules/bulk_create_rules_route.test.ts index 40955f2eba40a2..2e6cff31f8f7d5 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_crud/bulk_create_rules/bulk_create_rules_route.test.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_crud/bulk_create_rules/bulk_create_rules_route.test.ts @@ -26,7 +26,9 @@ describe('Bulk create rules request schema', () => { const result = BulkCreateRulesRequestBody.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"0: Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"0.name: Required, 0.description: Required, 0.risk_score: Required, 0.severity: Required, 0.type: Invalid literal value, expected \\"eql\\", and 52 more"` + ); }); test('single array element does validate', () => { @@ -56,7 +58,9 @@ describe('Bulk create rules request schema', () => { const result = BulkCreateRulesRequestBody.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"0: Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"0.risk_score: Required, 0.type: Invalid literal value, expected \\"eql\\", 0.language: Invalid literal value, expected \\"eql\\", 0.risk_score: Required, 0.risk_score: Required, and 22 more"` + ); }); test('two array elements where the first is valid but the second is invalid (risk_score) will not validate', () => { @@ -68,7 +72,9 @@ describe('Bulk create rules request schema', () => { const result = BulkCreateRulesRequestBody.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"1: Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"1.risk_score: Required, 1.type: Invalid literal value, expected \\"eql\\", 1.language: Invalid literal value, expected \\"eql\\", 1.risk_score: Required, 1.risk_score: Required, and 22 more"` + ); }); test('two array elements where the first is invalid (risk_score) but the second is valid will not validate', () => { @@ -80,7 +86,9 @@ describe('Bulk create rules request schema', () => { const result = BulkCreateRulesRequestBody.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"0: Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"0.risk_score: Required, 0.type: Invalid literal value, expected \\"eql\\", 0.language: Invalid literal value, expected \\"eql\\", 0.risk_score: Required, 0.risk_score: Required, and 22 more"` + ); }); test('two array elements where both are invalid (risk_score) will not validate', () => { @@ -95,7 +103,7 @@ describe('Bulk create rules request schema', () => { const result = BulkCreateRulesRequestBody.safeParse(payload); expectParseError(result); expect(stringifyZodError(result.error)).toMatchInlineSnapshot( - `"0: Invalid input, 1: Invalid input"` + `"0.risk_score: Required, 0.type: Invalid literal value, expected \\"eql\\", 0.language: Invalid literal value, expected \\"eql\\", 0.risk_score: Required, 0.risk_score: Required, and 49 more"` ); }); @@ -121,7 +129,9 @@ describe('Bulk create rules request schema', () => { const result = BulkCreateRulesRequestBody.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"0: Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"0.severity: Invalid enum value. Expected 'low' | 'medium' | 'high' | 'critical', received 'madeup', 0.type: Invalid literal value, expected \\"eql\\", 0.language: Invalid literal value, expected \\"eql\\", 0.severity: Invalid enum value. Expected 'low' | 'medium' | 'high' | 'critical', received 'madeup', 0.severity: Invalid enum value. Expected 'low' | 'medium' | 'high' | 'critical', received 'madeup', and 22 more"` + ); }); test('You can set "note" to a string', () => { @@ -154,6 +164,8 @@ describe('Bulk create rules request schema', () => { const result = BulkCreateRulesRequestBody.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"0: Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"0.note: Expected string, received object, 0.type: Invalid literal value, expected \\"eql\\", 0.language: Invalid literal value, expected \\"eql\\", 0.note: Expected string, received object, 0.note: Expected string, received object, and 22 more"` + ); }); }); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_crud/bulk_patch_rules/bulk_patch_rules_route.test.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_crud/bulk_patch_rules/bulk_patch_rules_route.test.ts index 443a3e0862b45a..d5325ad5ed13f5 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_crud/bulk_patch_rules/bulk_patch_rules_route.test.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_crud/bulk_patch_rules/bulk_patch_rules_route.test.ts @@ -70,6 +70,8 @@ describe('Bulk patch rules request schema', () => { const result = BulkPatchRulesRequestBody.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"1: Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"1.note: Expected string, received object, 1.note: Expected string, received object, 1.note: Expected string, received object, 1.note: Expected string, received object, 1.note: Expected string, received object, and 3 more"` + ); }); }); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_crud/bulk_update_rules/bulk_update_rules_route.test.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_crud/bulk_update_rules/bulk_update_rules_route.test.ts index 86a3a943b6626c..3fa69c6ad24dcd 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_crud/bulk_update_rules/bulk_update_rules_route.test.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_crud/bulk_update_rules/bulk_update_rules_route.test.ts @@ -27,7 +27,9 @@ describe('Bulk update rules request schema', () => { const result = BulkUpdateRulesRequestBody.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"0: Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"0.name: Required, 0.description: Required, 0.risk_score: Required, 0.severity: Required, 0.type: Invalid literal value, expected \\"eql\\", and 52 more"` + ); }); test('single array element does validate', () => { @@ -57,7 +59,9 @@ describe('Bulk update rules request schema', () => { const result = BulkUpdateRulesRequestBody.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"0: Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"0.risk_score: Required, 0.type: Invalid literal value, expected \\"eql\\", 0.language: Invalid literal value, expected \\"eql\\", 0.risk_score: Required, 0.risk_score: Required, and 22 more"` + ); }); test('two array elements where the first is valid but the second is invalid (risk_score) will not validate', () => { @@ -69,7 +73,9 @@ describe('Bulk update rules request schema', () => { const result = BulkUpdateRulesRequestBody.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"1: Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"1.risk_score: Required, 1.type: Invalid literal value, expected \\"eql\\", 1.language: Invalid literal value, expected \\"eql\\", 1.risk_score: Required, 1.risk_score: Required, and 22 more"` + ); }); test('two array elements where the first is invalid (risk_score) but the second is valid will not validate', () => { @@ -81,7 +87,9 @@ describe('Bulk update rules request schema', () => { const result = BulkUpdateRulesRequestBody.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"0: Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"0.risk_score: Required, 0.type: Invalid literal value, expected \\"eql\\", 0.language: Invalid literal value, expected \\"eql\\", 0.risk_score: Required, 0.risk_score: Required, and 22 more"` + ); }); test('two array elements where both are invalid (risk_score) will not validate', () => { @@ -96,7 +104,7 @@ describe('Bulk update rules request schema', () => { const result = BulkUpdateRulesRequestBody.safeParse(payload); expectParseError(result); expect(stringifyZodError(result.error)).toMatchInlineSnapshot( - `"0: Invalid input, 1: Invalid input"` + `"0.risk_score: Required, 0.type: Invalid literal value, expected \\"eql\\", 0.language: Invalid literal value, expected \\"eql\\", 0.risk_score: Required, 0.risk_score: Required, and 49 more"` ); }); @@ -122,7 +130,9 @@ describe('Bulk update rules request schema', () => { const result = BulkUpdateRulesRequestBody.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"0: Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"0.severity: Invalid enum value. Expected 'low' | 'medium' | 'high' | 'critical', received 'madeup', 0.type: Invalid literal value, expected \\"eql\\", 0.language: Invalid literal value, expected \\"eql\\", 0.severity: Invalid enum value. Expected 'low' | 'medium' | 'high' | 'critical', received 'madeup', 0.severity: Invalid enum value. Expected 'low' | 'medium' | 'high' | 'critical', received 'madeup', and 22 more"` + ); }); test('You can set "namespace" to a string', () => { @@ -165,6 +175,8 @@ describe('Bulk update rules request schema', () => { const result = BulkUpdateRulesRequestBody.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"0: Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"0.note: Expected string, received object, 0.type: Invalid literal value, expected \\"eql\\", 0.language: Invalid literal value, expected \\"eql\\", 0.note: Expected string, received object, 0.note: Expected string, received object, and 22 more"` + ); }); }); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_crud/response_schema.test.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_crud/response_schema.test.ts index d8e3c997e22790..413d83f9fee019 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_crud/response_schema.test.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_crud/response_schema.test.ts @@ -45,7 +45,9 @@ describe('Bulk CRUD rules response schema', () => { const result = BulkCrudRulesResponse.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"0: Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"0.error: Required, 0: Unrecognized key(s) in object: 'author', 'created_at', 'updated_at', 'created_by', 'description', 'enabled', 'false_positives', 'from', 'immutable', 'references', 'revision', 'severity', 'severity_mapping', 'updated_by', 'tags', 'to', 'threat', 'version', 'output_index', 'max_signals', 'risk_score', 'risk_score_mapping', 'interval', 'exceptions_list', 'related_integrations', 'required_fields', 'setup', 'throttle', 'actions', 'building_block_type', 'note', 'license', 'outcome', 'alias_target_id', 'alias_purpose', 'timeline_id', 'timeline_title', 'meta', 'rule_name_override', 'timestamp_override', 'timestamp_override_fallback_disabled', 'namespace', 'investigation_fields', 'query', 'type', 'language', 'index', 'data_view_id', 'filters', 'saved_id', 'response_actions', 'alert_suppression', 0.name: Required, 0.type: Invalid literal value, expected \\"eql\\", 0.language: Invalid literal value, expected \\"eql\\", and 24 more"` + ); }); test('it should NOT validate an invalid error message with a deleted value', () => { @@ -56,7 +58,9 @@ describe('Bulk CRUD rules response schema', () => { const result = BulkCrudRulesResponse.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"0: Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"0.error: Required, 0.name: Required, 0.description: Required, 0.risk_score: Required, 0.severity: Required, and 267 more"` + ); }); test('it should omit any extra rule props', () => { diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/patch_rule/patch_rule_route.test.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/patch_rule/patch_rule_route.test.ts index b70b5a6a7d9084..1994b3de5d453f 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/patch_rule/patch_rule_route.test.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/patch_rule/patch_rule_route.test.ts @@ -373,7 +373,9 @@ describe('Patch rule request schema', () => { const result = PatchRuleRequestBody.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"references.0: Expected string, received number, references.0: Expected string, received number, references.0: Expected string, received number, references.0: Expected string, received number, references.0: Expected string, received number, and 3 more"` + ); }); test('indexes cannot be numbers', () => { @@ -385,7 +387,9 @@ describe('Patch rule request schema', () => { const result = PatchRuleRequestBody.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"type: Invalid literal value, expected \\"eql\\", index.0: Expected string, received number, index.0: Expected string, received number, type: Invalid literal value, expected \\"saved_query\\", index.0: Expected string, received number, and 8 more"` + ); }); test('saved_id is not required when type is saved_query and will validate without it', () => { @@ -456,7 +460,9 @@ describe('Patch rule request schema', () => { const result = PatchRuleRequestBody.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", language: Invalid enum value. Expected 'kuery' | 'lucene', received 'something-made-up', type: Invalid literal value, expected \\"saved_query\\", language: Invalid enum value. Expected 'kuery' | 'lucene', received 'something-made-up', and 9 more"` + ); }); test('max_signals cannot be negative', () => { @@ -518,7 +524,9 @@ describe('Patch rule request schema', () => { const result = PatchRuleRequestBody.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"meta: Expected object, received string, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", meta: Expected object, received string, meta: Expected object, received string, and 12 more"` + ); }); test('filters cannot be a string', () => { @@ -529,7 +537,9 @@ describe('Patch rule request schema', () => { const result = PatchRuleRequestBody.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", filters: Expected array, received string, filters: Expected array, received string, type: Invalid literal value, expected \\"saved_query\\", and 10 more"` + ); }); test('name cannot be an empty string', () => { @@ -631,7 +641,9 @@ describe('Patch rule request schema', () => { const result = PatchRuleRequestBody.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"threat.0.framework: Required, threat.0.framework: Required, threat.0.framework: Required, threat.0.framework: Required, threat.0.framework: Required, and 3 more"` + ); }); test('threat is invalid when updated with missing tactic sub-object', () => { @@ -655,7 +667,9 @@ describe('Patch rule request schema', () => { const result = PatchRuleRequestBody.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"threat.0.tactic: Required, threat.0.tactic: Required, threat.0.tactic: Required, threat.0.tactic: Required, threat.0.tactic: Required, and 3 more"` + ); }); test('threat is valid when updated with missing technique', () => { @@ -700,7 +714,9 @@ describe('Patch rule request schema', () => { const result = PatchRuleRequestBody.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"severity: Invalid enum value. Expected 'low' | 'medium' | 'high' | 'critical', received 'junk', severity: Invalid enum value. Expected 'low' | 'medium' | 'high' | 'critical', received 'junk', severity: Invalid enum value. Expected 'low' | 'medium' | 'high' | 'critical', received 'junk', severity: Invalid enum value. Expected 'low' | 'medium' | 'high' | 'critical', received 'junk', severity: Invalid enum value. Expected 'low' | 'medium' | 'high' | 'critical', received 'junk', and 3 more"` + ); }); describe('note', () => { @@ -744,7 +760,9 @@ describe('Patch rule request schema', () => { const result = PatchRuleRequestBody.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"note: Expected string, received object, note: Expected string, received object, note: Expected string, received object, note: Expected string, received object, note: Expected string, received object, and 3 more"` + ); }); }); @@ -756,7 +774,9 @@ describe('Patch rule request schema', () => { const result = PatchRuleRequestBody.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"actions.0.group: Required, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", actions.0.group: Required, actions.0.group: Required, and 12 more"` + ); }); test('You cannot send in an array of actions that are missing "id"', () => { @@ -767,7 +787,9 @@ describe('Patch rule request schema', () => { const result = PatchRuleRequestBody.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"actions.0.id: Required, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", actions.0.id: Required, actions.0.id: Required, and 12 more"` + ); }); test('You cannot send in an array of actions that are missing "params"', () => { @@ -778,7 +800,9 @@ describe('Patch rule request schema', () => { const result = PatchRuleRequestBody.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"actions.0.params: Required, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", actions.0.params: Required, actions.0.params: Required, and 12 more"` + ); }); test('You cannot send in an array of actions that are including "actionTypeId"', () => { @@ -796,7 +820,9 @@ describe('Patch rule request schema', () => { const result = PatchRuleRequestBody.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"actions.0.action_type_id: Required, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", actions.0.action_type_id: Required, actions.0.action_type_id: Required, and 12 more"` + ); }); describe('exception_list', () => { @@ -862,7 +888,9 @@ describe('Patch rule request schema', () => { const result = PatchRuleRequestBody.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"exceptions_list.0.list_id: Required, exceptions_list.0.type: Required, exceptions_list.0.namespace_type: Invalid enum value. Expected 'agnostic' | 'single', received 'not a namespace type', type: Invalid literal value, expected \\"eql\\", exceptions_list.0.list_id: Required, and 26 more"` + ); }); test('[rule_id, description, from, to, index, name, severity, interval, type, filters, risk_score, note, and non-existent exceptions_list] does validate with empty exceptions_list', () => { diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/export_rules/export_rules_route.gen.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/export_rules/export_rules_route.gen.ts index 01cd91216753f4..2dfb54396c9ce0 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/export_rules/export_rules_route.gen.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/export_rules/export_rules_route.gen.ts @@ -6,6 +6,7 @@ */ import { z } from 'zod'; +import { BooleanFromString } from '@kbn/zod-helpers'; /* * NOTICE: Do not edit this file manually. @@ -19,13 +20,7 @@ export const ExportRulesRequestQuery = z.object({ /** * Determines whether a summary of the exported rules is returned. */ - exclude_export_details: z.preprocess( - (value: unknown) => (typeof value === 'boolean' ? String(value) : value), - z - .enum(['true', 'false']) - .default('false') - .transform((value) => value === 'true') - ), + exclude_export_details: BooleanFromString.optional().default(false), /** * File name for saving the exported rules. */ diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/export_rules/export_rules_route.test.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/export_rules/export_rules_route.test.ts index 49b56c66732185..783556d2076af3 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/export_rules/export_rules_route.test.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/export_rules/export_rules_route.test.ts @@ -120,7 +120,7 @@ describe('Export rules request schema', () => { const result = ExportRulesRequestQuery.safeParse(payload); expectParseError(result); expect(stringifyZodError(result.error)).toEqual( - `exclude_export_details: Invalid enum value. Expected 'true' | 'false', received 'invalid string'` + `exclude_export_details: Invalid enum value. Expected 'true' | 'false', received 'invalid string', exclude_export_details: Expected boolean, received string` ); }); }); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/find_rules/find_rules_route.gen.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/find_rules/find_rules_route.gen.ts new file mode 100644 index 00000000000000..a515cf70f95130 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/find_rules/find_rules_route.gen.ts @@ -0,0 +1,71 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; +import { ArrayFromString } from '@kbn/zod-helpers'; + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + */ + +import { SortOrder } from '../../model/sorting.gen'; +import { RuleResponse } from '../../model/rule_schema/rule_schemas.gen'; + +export type FindRulesSortField = z.infer; +export const FindRulesSortField = z.enum([ + 'created_at', + 'createdAt', + 'enabled', + 'execution_summary.last_execution.date', + 'execution_summary.last_execution.metrics.execution_gap_duration_s', + 'execution_summary.last_execution.metrics.total_indexing_duration_ms', + 'execution_summary.last_execution.metrics.total_search_duration_ms', + 'execution_summary.last_execution.status', + 'name', + 'risk_score', + 'riskScore', + 'severity', + 'updated_at', + 'updatedAt', +]); +export type FindRulesSortFieldEnum = typeof FindRulesSortField.enum; +export const FindRulesSortFieldEnum = FindRulesSortField.enum; + +export type FindRulesRequestQuery = z.infer; +export const FindRulesRequestQuery = z.object({ + fields: ArrayFromString(z.string()).optional(), + /** + * Search query + */ + filter: z.string().optional(), + /** + * Field to sort by + */ + sort_field: FindRulesSortField.optional(), + /** + * Sort order + */ + sort_order: SortOrder.optional(), + /** + * Page number + */ + page: z.coerce.number().int().min(1).optional().default(1), + /** + * Rules per page + */ + per_page: z.coerce.number().int().min(0).optional().default(20), +}); +export type FindRulesRequestQueryInput = z.input; + +export type FindRulesResponse = z.infer; +export const FindRulesResponse = z.object({ + page: z.number().int(), + perPage: z.number().int(), + total: z.number().int(), + data: z.array(RuleResponse), +}); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/find_rules/find_rules_route.schema.yaml b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/find_rules/find_rules_route.schema.yaml new file mode 100644 index 00000000000000..4fa1c14542ed0a --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/find_rules/find_rules_route.schema.yaml @@ -0,0 +1,98 @@ +openapi: 3.0.0 +info: + title: Find Rules API endpoint + version: 2023-10-31 +paths: + /api/detection_engine/rules/_find: + get: + operationId: FindRules + x-codegen-enabled: true + description: Finds rules that match the given query. + tags: + - Rules API + parameters: + - name: 'fields' + in: query + required: false + schema: + type: array + items: + type: string + - name: 'filter' + in: query + description: Search query + required: false + schema: + type: string + - name: 'sort_field' + in: query + description: Field to sort by + required: false + schema: + $ref: '#/components/schemas/FindRulesSortField' + - name: 'sort_order' + in: query + description: Sort order + required: false + schema: + $ref: '../../model/sorting.schema.yaml#/components/schemas/SortOrder' + - name: 'page' + in: query + description: Page number + required: false + schema: + type: integer + minimum: 1 + default: 1 + - name: 'per_page' + in: query + description: Rules per page + required: false + schema: + type: integer + minimum: 0 + default: 20 + + responses: + '200': + description: Successful response + content: + application/json: + schema: + type: object + properties: + page: + type: integer + perPage: + type: integer + total: + type: integer + data: + type: array + items: + $ref: '../../model/rule_schema/rule_schemas.schema.yaml#/components/schemas/RuleResponse' + required: + - page + - perPage + - total + - data + +components: + schemas: + FindRulesSortField: + type: string + enum: + - 'created_at' + - 'createdAt' # Legacy notation, keeping for backwards compatibility + - 'enabled' + - 'execution_summary.last_execution.date' + - 'execution_summary.last_execution.metrics.execution_gap_duration_s' + - 'execution_summary.last_execution.metrics.total_indexing_duration_ms' + - 'execution_summary.last_execution.metrics.total_search_duration_ms' + - 'execution_summary.last_execution.status' + - 'name' + - 'risk_score' + - 'riskScore' # Legacy notation, keeping for backwards compatibility + - 'severity' + - 'updated_at' + - 'updatedAt' # Legacy notation, keeping for backwards compatibility diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/find_rules/find_rules_route.test.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/find_rules/find_rules_route.test.ts index 391826fed99232..aa4bb53ab7481f 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/find_rules/find_rules_route.test.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/find_rules/find_rules_route.test.ts @@ -5,21 +5,17 @@ * 2.0. */ -import { left } from 'fp-ts/lib/Either'; -import { pipe } from 'fp-ts/lib/pipeable'; -import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; - -import { FindRulesRequestQuery } from './find_rules_route'; +import { expectParseError, expectParseSuccess, stringifyZodError } from '@kbn/zod-helpers'; +import type { FindRulesRequestQueryInput } from './find_rules_route.gen'; +import { FindRulesRequestQuery } from './find_rules_route.gen'; describe('Find rules request schema', () => { test('empty objects do validate', () => { - const payload: FindRulesRequestQuery = {}; + const payload: FindRulesRequestQueryInput = {}; - const decoded = FindRulesRequestQuery.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual({ + const result = FindRulesRequestQuery.safeParse(payload); + expectParseSuccess(result); + expect(result.data).toEqual({ page: 1, per_page: 20, }); @@ -35,167 +31,126 @@ describe('Find rules request schema', () => { sort_order: 'asc', }; - const decoded = FindRulesRequestQuery.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); + const result = FindRulesRequestQuery.safeParse(payload); + expectParseSuccess(result); + expect(result.data).toEqual(payload); }); - test('made up parameters do not validate', () => { - const payload: Partial & { madeUp: string } = { + test('made up parameters are ignored', () => { + const payload: Partial & { madeUp: string } = { madeUp: 'invalid value', }; - const decoded = FindRulesRequestQuery.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual(['invalid keys "madeUp"']); - expect(message.schema).toEqual({}); + const result = FindRulesRequestQuery.safeParse(payload); + expectParseSuccess(result); + expect(result.data).toEqual({ + page: 1, + per_page: 20, + }); }); test('per_page validates', () => { - const payload: FindRulesRequestQuery = { + const payload: FindRulesRequestQueryInput = { per_page: 5, }; - const decoded = FindRulesRequestQuery.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as FindRulesRequestQuery).per_page).toEqual(payload.per_page); + const result = FindRulesRequestQuery.safeParse(payload); + expectParseSuccess(result); + expect(result.data.per_page).toEqual(payload.per_page); }); test('page validates', () => { - const payload: FindRulesRequestQuery = { + const payload: FindRulesRequestQueryInput = { page: 5, }; - const decoded = FindRulesRequestQuery.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as FindRulesRequestQuery).page).toEqual(payload.page); + const result = FindRulesRequestQuery.safeParse(payload); + expectParseSuccess(result); + expect(result.data.page).toEqual(payload.page); }); test('sort_field validates', () => { - const payload: FindRulesRequestQuery = { + const payload: FindRulesRequestQueryInput = { sort_field: 'name', }; - const decoded = FindRulesRequestQuery.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as FindRulesRequestQuery).sort_field).toEqual('name'); + const result = FindRulesRequestQuery.safeParse(payload); + expectParseSuccess(result); + expect(result.data.sort_field).toEqual(payload.sort_field); }); test('fields validates with a string', () => { - const payload: FindRulesRequestQuery = { + const payload: FindRulesRequestQueryInput = { fields: ['some value'], }; - const decoded = FindRulesRequestQuery.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as FindRulesRequestQuery).fields).toEqual(payload.fields); + const result = FindRulesRequestQuery.safeParse(payload); + expectParseSuccess(result); + expect(result.data.fields).toEqual(payload.fields); }); test('fields validates with multiple strings', () => { - const payload: FindRulesRequestQuery = { + const payload: FindRulesRequestQueryInput = { fields: ['some value 1', 'some value 2'], }; - const decoded = FindRulesRequestQuery.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as FindRulesRequestQuery).fields).toEqual(payload.fields); + const result = FindRulesRequestQuery.safeParse(payload); + expectParseSuccess(result); + expect(result.data.fields).toEqual(payload.fields); }); test('fields does not validate with a number', () => { - const payload: Omit & { fields: number } = { + const payload: Omit & { fields: number } = { fields: 5, }; - const decoded = FindRulesRequestQuery.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual(['Invalid value "5" supplied to "fields"']); - expect(message.schema).toEqual({}); - }); - - test('per_page has a default of 20', () => { - const payload: FindRulesRequestQuery = {}; - - const decoded = FindRulesRequestQuery.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as FindRulesRequestQuery).per_page).toEqual(20); - }); - - test('page has a default of 1', () => { - const payload: FindRulesRequestQuery = {}; - - const decoded = FindRulesRequestQuery.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as FindRulesRequestQuery).page).toEqual(1); + const result = FindRulesRequestQuery.safeParse(payload); + expectParseError(result); + expect(stringifyZodError(result.error)).toEqual('fields: Expected array, received number'); }); test('filter works with a string', () => { - const payload: FindRulesRequestQuery = { + const payload: FindRulesRequestQueryInput = { filter: 'some value 1', }; - const decoded = FindRulesRequestQuery.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as FindRulesRequestQuery).filter).toEqual(payload.filter); + const result = FindRulesRequestQuery.safeParse(payload); + expectParseSuccess(result); + expect(result.data.filter).toEqual(payload.filter); }); test('filter does not work with a number', () => { - const payload: Omit & { filter: number } = { + const payload: Omit & { filter: number } = { filter: 5, }; - const decoded = FindRulesRequestQuery.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual(['Invalid value "5" supplied to "filter"']); - expect(message.schema).toEqual({}); + const result = FindRulesRequestQuery.safeParse(payload); + expectParseError(result); + expect(stringifyZodError(result.error)).toEqual('filter: Expected string, received number'); }); test('sort_order validates with desc and sort_field', () => { - const payload: FindRulesRequestQuery = { + const payload: FindRulesRequestQueryInput = { sort_order: 'desc', sort_field: 'name', }; - const decoded = FindRulesRequestQuery.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as FindRulesRequestQuery).sort_order).toEqual(payload.sort_order); - expect((message.schema as FindRulesRequestQuery).sort_field).toEqual(payload.sort_field); + const result = FindRulesRequestQuery.safeParse(payload); + expectParseSuccess(result); + expect(result.data.sort_order).toEqual(payload.sort_order); + expect(result.data.sort_field).toEqual(payload.sort_field); }); test('sort_order does not validate with a string other than asc and desc', () => { - const payload: Omit & { sort_order: string } = { + const payload: Omit & { sort_order: string } = { sort_order: 'some other string', sort_field: 'name', }; - const decoded = FindRulesRequestQuery.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "some other string" supplied to "sort_order"', - ]); - expect(message.schema).toEqual({}); + const result = FindRulesRequestQuery.safeParse(payload); + expectParseError(result); + expect(stringifyZodError(result.error)).toEqual( + "sort_order: Invalid enum value. Expected 'asc' | 'desc', received 'some other string'" + ); }); }); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/find_rules/find_rules_route.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/find_rules/find_rules_route.ts deleted file mode 100644 index 7e16b696bdd701..00000000000000 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/find_rules/find_rules_route.ts +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as t from 'io-ts'; -import { DefaultPerPage, DefaultPage } from '@kbn/securitysolution-io-ts-alerting-types'; -import type { RuleResponse } from '../../model'; -import { SortOrder, queryFilter, fields } from '../../model'; - -export type FindRulesSortField = t.TypeOf; -export const FindRulesSortField = t.union([ - t.literal('created_at'), - t.literal('createdAt'), // Legacy notation, keeping for backwards compatibility - t.literal('enabled'), - t.literal('execution_summary.last_execution.date'), - t.literal('execution_summary.last_execution.metrics.execution_gap_duration_s'), - t.literal('execution_summary.last_execution.metrics.total_indexing_duration_ms'), - t.literal('execution_summary.last_execution.metrics.total_search_duration_ms'), - t.literal('execution_summary.last_execution.status'), - t.literal('name'), - t.literal('risk_score'), - t.literal('riskScore'), // Legacy notation, keeping for backwards compatibility - t.literal('severity'), - t.literal('updated_at'), - t.literal('updatedAt'), // Legacy notation, keeping for backwards compatibility -]); - -export type FindRulesSortFieldOrUndefined = t.TypeOf; -export const FindRulesSortFieldOrUndefined = t.union([FindRulesSortField, t.undefined]); - -/** - * Query string parameters of the API route. - */ -export type FindRulesRequestQuery = t.TypeOf; -export const FindRulesRequestQuery = t.exact( - t.partial({ - fields, - filter: queryFilter, - sort_field: FindRulesSortField, - sort_order: SortOrder, - page: DefaultPage, // defaults to 1 - per_page: DefaultPerPage, // defaults to 20 - }) -); - -export interface FindRulesResponse { - page: number; - perPage: number; - total: number; - data: RuleResponse[]; -} diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/find_rules/request_schema_validation.test.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/find_rules/request_schema_validation.test.ts index 93cded33f6d94e..c9fb9ce9a35243 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/find_rules/request_schema_validation.test.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/find_rules/request_schema_validation.test.ts @@ -5,19 +5,19 @@ * 2.0. */ -import type { FindRulesRequestQuery } from './find_rules_route'; +import type { FindRulesRequestQueryInput } from './find_rules_route.gen'; import { validateFindRulesRequestQuery } from './request_schema_validation'; describe('Find rules request schema, additional validation', () => { describe('validateFindRulesRequestQuery', () => { test('You can have an empty sort_field and empty sort_order', () => { - const schema: FindRulesRequestQuery = {}; + const schema: FindRulesRequestQueryInput = {}; const errors = validateFindRulesRequestQuery(schema); expect(errors).toEqual([]); }); test('You can have both a sort_field and and a sort_order', () => { - const schema: FindRulesRequestQuery = { + const schema: FindRulesRequestQueryInput = { sort_field: 'name', sort_order: 'asc', }; @@ -26,7 +26,7 @@ describe('Find rules request schema, additional validation', () => { }); test('You cannot have sort_field without sort_order', () => { - const schema: FindRulesRequestQuery = { + const schema: FindRulesRequestQueryInput = { sort_field: 'name', }; const errors = validateFindRulesRequestQuery(schema); @@ -36,7 +36,7 @@ describe('Find rules request schema, additional validation', () => { }); test('You cannot have sort_order without sort_field', () => { - const schema: FindRulesRequestQuery = { + const schema: FindRulesRequestQueryInput = { sort_order: 'asc', }; const errors = validateFindRulesRequestQuery(schema); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/find_rules/request_schema_validation.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/find_rules/request_schema_validation.ts index 769ef566d1efd9..69d94be334e3fd 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/find_rules/request_schema_validation.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/find_rules/request_schema_validation.ts @@ -5,23 +5,16 @@ * 2.0. */ -import type { FindRulesRequestQuery } from './find_rules_route'; +import type { FindRulesRequestQueryInput } from './find_rules_route.gen'; /** * Additional validation that is implemented outside of the schema itself. */ -export const validateFindRulesRequestQuery = (query: FindRulesRequestQuery): string[] => { - return [...validateSortOrder(query)]; -}; - -const validateSortOrder = (query: FindRulesRequestQuery): string[] => { +export const validateFindRulesRequestQuery = (query: FindRulesRequestQueryInput): string[] => { if (query.sort_order != null || query.sort_field != null) { if (query.sort_order == null || query.sort_field == null) { return ['when "sort_order" and "sort_field" must exist together or not at all']; - } else { - return []; } - } else { - return []; } + return []; }; diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/import_rules/import_rules_route.gen.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/import_rules/import_rules_route.gen.ts index d0a105e28c2c8c..225179a6c04891 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/import_rules/import_rules_route.gen.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/import_rules/import_rules_route.gen.ts @@ -6,6 +6,7 @@ */ import { z } from 'zod'; +import { BooleanFromString } from '@kbn/zod-helpers'; /* * NOTICE: Do not edit this file manually. @@ -20,43 +21,19 @@ export const ImportRulesRequestQuery = z.object({ /** * Determines whether existing rules with the same `rule_id` are overwritten. */ - overwrite: z.preprocess( - (value: unknown) => (typeof value === 'boolean' ? String(value) : value), - z - .enum(['true', 'false']) - .default('false') - .transform((value) => value === 'true') - ), + overwrite: BooleanFromString.optional().default(false), /** * Determines whether existing exception lists with the same `list_id` are overwritten. */ - overwrite_exceptions: z.preprocess( - (value: unknown) => (typeof value === 'boolean' ? String(value) : value), - z - .enum(['true', 'false']) - .default('false') - .transform((value) => value === 'true') - ), + overwrite_exceptions: BooleanFromString.optional().default(false), /** * Determines whether existing actions with the same `kibana.alert.rule.actions.id` are overwritten. */ - overwrite_action_connectors: z.preprocess( - (value: unknown) => (typeof value === 'boolean' ? String(value) : value), - z - .enum(['true', 'false']) - .default('false') - .transform((value) => value === 'true') - ), + overwrite_action_connectors: BooleanFromString.optional().default(false), /** * Generates a new list ID for each imported exception list. */ - as_new_list: z.preprocess( - (value: unknown) => (typeof value === 'boolean' ? String(value) : value), - z - .enum(['true', 'false']) - .default('false') - .transform((value) => value === 'true') - ), + as_new_list: BooleanFromString.optional().default(false), }); export type ImportRulesRequestQueryInput = z.input; diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/index.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/index.ts index 7fdab0816d650f..709e63a6ec4023 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/index.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/index.ts @@ -5,7 +5,8 @@ * 2.0. */ -export * from './bulk_actions/bulk_actions_route'; +export * from './bulk_actions/bulk_actions_types'; +export * from './bulk_actions/bulk_actions_route.gen'; export * from './bulk_crud/bulk_create_rules/bulk_create_rules_route.gen'; export * from './bulk_crud/bulk_delete_rules/bulk_delete_rules_route.gen'; export * from './bulk_crud/bulk_patch_rules/bulk_patch_rules_route.gen'; @@ -22,7 +23,7 @@ export * from './crud/update_rule/request_schema_validation'; export * from './crud/update_rule/update_rule_route.gen'; export * from './export_rules/export_rules_details_schema'; export * from './export_rules/export_rules_route.gen'; -export * from './find_rules/find_rules_route'; +export * from './find_rules/find_rules_route.gen'; export * from './find_rules/request_schema_validation'; export * from './get_rule_management_filters/get_rule_management_filters_route'; export * from './import_rules/import_rules_route.gen'; diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/index.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/index.ts index 1494e09b9c51ac..ddb132ebf64bbb 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/index.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/index.ts @@ -10,15 +10,15 @@ export * from './detection_engine_health/get_rule_health/get_rule_health_route'; export * from './detection_engine_health/get_space_health/get_space_health_route'; export * from './detection_engine_health/setup_health/setup_health_route'; export * from './detection_engine_health/model'; -export * from './rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route'; +export * from './rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.gen'; export * from './rule_execution_logs/get_rule_execution_results/get_rule_execution_results_route.gen'; export * from './urls'; -export * from './model/execution_event'; -export * from './model/execution_metrics'; +export * from './model/execution_event.gen'; +export * from './model/execution_metrics.gen'; export * from './model/execution_result.gen'; export * from './model/execution_settings'; export * from './model/execution_status.gen'; export * from './model/execution_status'; -export * from './model/execution_summary'; +export * from './model/execution_summary.gen'; export * from './model/log_level'; diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_event.gen.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_event.gen.ts new file mode 100644 index 00000000000000..e493c5233a03d2 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_event.gen.ts @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + */ + +export type LogLevel = z.infer; +export const LogLevel = z.enum(['trace', 'debug', 'info', 'warn', 'error']); +export type LogLevelEnum = typeof LogLevel.enum; +export const LogLevelEnum = LogLevel.enum; + +/** + * Type of a plain rule execution event: +- message: Simple log message of some log level, such as debug, info or error. +- status-change: We log an event of this type each time a rule changes its status during an execution. +- execution-metrics: We log an event of this type at the end of a rule execution. It contains various execution metrics such as search and indexing durations. + */ +export type RuleExecutionEventType = z.infer; +export const RuleExecutionEventType = z.enum(['message', 'status-change', 'execution-metrics']); +export type RuleExecutionEventTypeEnum = typeof RuleExecutionEventType.enum; +export const RuleExecutionEventTypeEnum = RuleExecutionEventType.enum; + +/** + * Plain rule execution event. A rule can write many of them during each execution. Events can be of different types and log levels. + +NOTE: This is a read model of rule execution events and it is pretty generic. It contains only a subset of their fields: only those fields that are common to all types of execution events. + */ +export type RuleExecutionEvent = z.infer; +export const RuleExecutionEvent = z.object({ + timestamp: z.string().datetime(), + sequence: z.number().int(), + level: LogLevel, + type: RuleExecutionEventType, + execution_id: z.string().min(1), + message: z.string(), +}); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_event.mock.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_event.mock.ts index c8efaa8dd85b8c..3d987356a37c93 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_event.mock.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_event.mock.ts @@ -5,9 +5,8 @@ * 2.0. */ -import type { RuleExecutionEvent } from './execution_event'; -import { RuleExecutionEventType } from './execution_event'; -import { LogLevel } from './log_level'; +import type { RuleExecutionEvent } from './execution_event.gen'; +import { LogLevelEnum, RuleExecutionEventTypeEnum } from './execution_event.gen'; const DEFAULT_TIMESTAMP = '2021-12-28T10:10:00.806Z'; const DEFAULT_SEQUENCE_NUMBER = 0; @@ -17,13 +16,13 @@ const getMessageEvent = (props: Partial = {}): RuleExecution // Default values timestamp: DEFAULT_TIMESTAMP, sequence: DEFAULT_SEQUENCE_NUMBER, - level: LogLevel.debug, + level: LogLevelEnum.debug, execution_id: 'execution-id-1', message: 'Some message', // Overridden values ...props, // Mandatory values for this type of event - type: RuleExecutionEventType.message, + type: RuleExecutionEventTypeEnum.message, }; }; @@ -37,8 +36,8 @@ const getRunningStatusChange = (props: Partial = {}): RuleEx // Overridden values ...props, // Mandatory values for this type of event - level: LogLevel.info, - type: RuleExecutionEventType['status-change'], + level: LogLevelEnum.info, + type: RuleExecutionEventTypeEnum['status-change'], }; }; @@ -54,8 +53,8 @@ const getPartialFailureStatusChange = ( // Overridden values ...props, // Mandatory values for this type of event - level: LogLevel.warn, - type: RuleExecutionEventType['status-change'], + level: LogLevelEnum.warn, + type: RuleExecutionEventTypeEnum['status-change'], }; }; @@ -69,8 +68,8 @@ const getFailedStatusChange = (props: Partial = {}): RuleExe // Overridden values ...props, // Mandatory values for this type of event - level: LogLevel.error, - type: RuleExecutionEventType['status-change'], + level: LogLevelEnum.error, + type: RuleExecutionEventTypeEnum['status-change'], }; }; @@ -84,8 +83,8 @@ const getSucceededStatusChange = (props: Partial = {}): Rule // Overridden values ...props, // Mandatory values for this type of event - level: LogLevel.info, - type: RuleExecutionEventType['status-change'], + level: LogLevelEnum.info, + type: RuleExecutionEventTypeEnum['status-change'], }; }; @@ -99,8 +98,8 @@ const getExecutionMetricsEvent = (props: Partial = {}): Rule // Overridden values ...props, // Mandatory values for this type of event - level: LogLevel.debug, - type: RuleExecutionEventType['execution-metrics'], + level: LogLevelEnum.debug, + type: RuleExecutionEventTypeEnum['execution-metrics'], }; }; @@ -120,7 +119,7 @@ const getSomeEvents = (): RuleExecutionEvent[] => [ getMessageEvent({ timestamp: '2021-12-28T10:10:06.806Z', sequence: 6, - level: LogLevel.debug, + level: LogLevelEnum.debug, message: 'Rule execution started', }), getFailedStatusChange({ @@ -138,7 +137,7 @@ const getSomeEvents = (): RuleExecutionEvent[] => [ getMessageEvent({ timestamp: '2021-12-28T10:10:02.806Z', sequence: 2, - level: LogLevel.error, + level: LogLevelEnum.error, message: 'Some error', }), getRunningStatusChange({ @@ -148,7 +147,7 @@ const getSomeEvents = (): RuleExecutionEvent[] => [ getMessageEvent({ timestamp: '2021-12-28T10:10:00.806Z', sequence: 0, - level: LogLevel.debug, + level: LogLevelEnum.debug, message: 'Rule execution started', }), ]; diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_event.schema.yaml b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_event.schema.yaml new file mode 100644 index 00000000000000..d49a49d2224011 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_event.schema.yaml @@ -0,0 +1,49 @@ +openapi: '3.0.0' +info: + title: Execution Event Schema + version: 'not applicable' +paths: {} +components: + x-codegen-enabled: true + schemas: + LogLevel: + type: string + enum: ['trace', 'debug', 'info', 'warn', 'error'] + + RuleExecutionEventType: + type: string + enum: ['message', 'status-change', 'execution-metrics'] + description: |- + Type of a plain rule execution event: + - message: Simple log message of some log level, such as debug, info or error. + - status-change: We log an event of this type each time a rule changes its status during an execution. + - execution-metrics: We log an event of this type at the end of a rule execution. It contains various execution metrics such as search and indexing durations. + + RuleExecutionEvent: + type: object + properties: + timestamp: + type: string + format: date-time + sequence: + type: integer + level: + $ref: '#/components/schemas/LogLevel' + type: + $ref: '#/components/schemas/RuleExecutionEventType' + execution_id: + type: string + minLength: 1 + message: + type: string + required: + - timestamp + - sequence + - level + - type + - execution_id + - message + description: |- + Plain rule execution event. A rule can write many of them during each execution. Events can be of different types and log levels. + + NOTE: This is a read model of rule execution events and it is pretty generic. It contains only a subset of their fields: only those fields that are common to all types of execution events. diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_event.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_event.ts deleted file mode 100644 index 64acfb01e2e2a5..00000000000000 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_event.ts +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as t from 'io-ts'; -import { enumeration, IsoDateString, NonEmptyString } from '@kbn/securitysolution-io-ts-types'; -import { enumFromString } from '../../../../utils/enum_from_string'; -import { TLogLevel } from './log_level'; - -/** - * Type of a plain rule execution event. - */ -export enum RuleExecutionEventType { - /** - * Simple log message of some log level, such as debug, info or error. - */ - 'message' = 'message', - - /** - * We log an event of this type each time a rule changes its status during an execution. - */ - 'status-change' = 'status-change', - - /** - * We log an event of this type at the end of a rule execution. It contains various execution - * metrics such as search and indexing durations. - */ - 'execution-metrics' = 'execution-metrics', -} - -export const TRuleExecutionEventType = enumeration( - 'RuleExecutionEventType', - RuleExecutionEventType -); - -/** - * An array of supported types of rule execution events. - */ -export const RULE_EXECUTION_EVENT_TYPES = Object.values(RuleExecutionEventType); - -export const ruleExecutionEventTypeFromString = enumFromString(RuleExecutionEventType); - -/** - * Plain rule execution event. A rule can write many of them during each execution. Events can be - * of different types and log levels. - * - * NOTE: This is a read model of rule execution events and it is pretty generic. It contains only a - * subset of their fields: only those fields that are common to all types of execution events. - */ -export type RuleExecutionEvent = t.TypeOf; -export const RuleExecutionEvent = t.type({ - timestamp: IsoDateString, - sequence: t.number, - level: TLogLevel, - type: TRuleExecutionEventType, - execution_id: NonEmptyString, - message: t.string, -}); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_metrics.gen.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_metrics.gen.ts index 235437cc5ed68f..67aac49310a7d7 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_metrics.gen.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_metrics.gen.ts @@ -15,16 +15,19 @@ import { z } from 'zod'; export type RuleExecutionMetrics = z.infer; export const RuleExecutionMetrics = z.object({ /** - * Total time spent searching for events + * Total time spent performing ES searches as measured by Kibana; includes network latency and time spent serializing/deserializing request/response */ total_search_duration_ms: z.number().int().min(0).optional(), /** - * Total time spent indexing alerts + * Total time spent indexing documents during current rule execution cycle */ total_indexing_duration_ms: z.number().int().min(0).optional(), + /** + * Total time spent enriching documents during current rule execution cycle + */ total_enrichment_duration_ms: z.number().int().min(0).optional(), /** - * Time gap between last execution and current execution + * Duration in seconds of execution gap */ execution_gap_duration_s: z.number().int().min(0).optional(), }); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_metrics.schema.yaml b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_metrics.schema.yaml index 7e04ef38a0a875..985da08e1df880 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_metrics.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_metrics.schema.yaml @@ -10,17 +10,18 @@ components: type: object properties: total_search_duration_ms: - description: Total time spent searching for events + description: Total time spent performing ES searches as measured by Kibana; includes network latency and time spent serializing/deserializing request/response type: integer minimum: 0 total_indexing_duration_ms: - description: Total time spent indexing alerts + description: Total time spent indexing documents during current rule execution cycle type: integer minimum: 0 total_enrichment_duration_ms: + description: Total time spent enriching documents during current rule execution cycle type: integer minimum: 0 execution_gap_duration_s: - description: Time gap between last execution and current execution + description: Duration in seconds of execution gap type: integer minimum: 0 diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_metrics.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_metrics.ts deleted file mode 100644 index b15c76119e4413..00000000000000 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_metrics.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as t from 'io-ts'; -import { PositiveInteger } from '@kbn/securitysolution-io-ts-types'; - -export type DurationMetric = t.TypeOf; -export const DurationMetric = PositiveInteger; - -export type RuleExecutionMetrics = t.TypeOf; - -/** - @property total_search_duration_ms - "total time spent performing ES searches as measured by Kibana; - includes network latency and time spent serializing/deserializing request/response", - @property total_indexing_duration_ms - "total time spent indexing documents during current rule execution cycle", - @property total_enrichment_duration_ms - total time spent enriching documents during current rule execution cycle - @property execution_gap_duration_s - "duration in seconds of execution gap" -*/ -export const RuleExecutionMetrics = t.partial({ - total_search_duration_ms: DurationMetric, - total_indexing_duration_ms: DurationMetric, - total_enrichment_duration_ms: DurationMetric, - execution_gap_duration_s: DurationMetric, -}); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_status.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_status.ts index ae031191fd74d9..903912b39cbb25 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_status.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_status.ts @@ -6,14 +6,10 @@ */ import type { RuleLastRunOutcomes } from '@kbn/alerting-plugin/common'; -import { enumeration } from '@kbn/securitysolution-io-ts-types'; import { assertUnreachable } from '../../../../utility_types'; import type { RuleExecutionStatus, RuleExecutionStatusOrder } from './execution_status.gen'; import { RuleExecutionStatusEnum } from './execution_status.gen'; -// TODO remove after the migration to Zod is done -export const TRuleExecutionStatus = enumeration('RuleExecutionStatus', RuleExecutionStatusEnum); - export const ruleExecutionStatusToNumber = ( status: RuleExecutionStatus ): RuleExecutionStatusOrder => { diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_summary.mock.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_summary.mock.ts index 59482e759f902f..5ffc034edd1723 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_summary.mock.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_summary.mock.ts @@ -6,7 +6,7 @@ */ import { RuleExecutionStatusEnum } from './execution_status.gen'; -import type { RuleExecutionSummary } from './execution_summary'; +import type { RuleExecutionSummary } from './execution_summary.gen'; const getSummarySucceeded = (): RuleExecutionSummary => ({ last_execution: { diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_summary.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_summary.ts deleted file mode 100644 index a747d2f021b7c5..00000000000000 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_summary.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { IsoDateString } from '@kbn/securitysolution-io-ts-types'; -import * as t from 'io-ts'; -import { RuleExecutionMetrics } from './execution_metrics'; -import { TRuleExecutionStatus } from './execution_status'; - -export type RuleExecutionSummary = t.TypeOf; -export const RuleExecutionSummary = t.type({ - last_execution: t.type({ - date: IsoDateString, - status: TRuleExecutionStatus, - status_order: t.number, - message: t.string, - metrics: RuleExecutionMetrics, - }), -}); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/index.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/index.ts index b4f003cf48228c..7fbb16e2061979 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/index.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/index.ts @@ -5,10 +5,10 @@ * 2.0. */ -export * from './execution_event'; -export * from './execution_metrics'; +export * from './execution_event.gen'; +export * from './execution_metrics.gen'; export * from './execution_result.gen'; export * from './execution_settings'; export * from './execution_status.gen'; -export * from './execution_summary'; +export * from './execution_summary.gen'; export * from './log_level'; diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/log_level.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/log_level.ts index 495589b3cd432a..e7004455160bde 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/log_level.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/log_level.ts @@ -5,42 +5,31 @@ * 2.0. */ -import { enumeration } from '@kbn/securitysolution-io-ts-types'; -import { enumFromString } from '../../../../utils/enum_from_string'; import { assertUnreachable } from '../../../../utility_types'; import type { RuleExecutionStatus } from './execution_status.gen'; import { RuleExecutionStatusEnum } from './execution_status.gen'; - -export enum LogLevel { - 'trace' = 'trace', - 'debug' = 'debug', - 'info' = 'info', - 'warn' = 'warn', - 'error' = 'error', -} - -export const TLogLevel = enumeration('LogLevel', LogLevel); +import { LogLevel, LogLevelEnum } from './execution_event.gen'; /** * An array of supported log levels. */ -export const LOG_LEVELS = Object.values(LogLevel); +export const LOG_LEVELS = LogLevel.options; -export const logLevelToNumber = (level: keyof typeof LogLevel | null | undefined): number => { +export const logLevelToNumber = (level: LogLevel | null | undefined): number => { if (!level) { return 0; } switch (level) { - case 'trace': + case LogLevelEnum.trace: return 0; - case 'debug': + case LogLevelEnum.debug: return 10; - case 'info': + case LogLevelEnum.info: return 20; - case 'warn': + case LogLevelEnum.warn: return 30; - case 'error': + case LogLevelEnum.error: return 40; default: assertUnreachable(level); @@ -50,34 +39,32 @@ export const logLevelToNumber = (level: keyof typeof LogLevel | null | undefined export const logLevelFromNumber = (num: number | null | undefined): LogLevel => { if (num === null || num === undefined || num < 10) { - return LogLevel.trace; + return LogLevelEnum.trace; } if (num < 20) { - return LogLevel.debug; + return LogLevelEnum.debug; } if (num < 30) { - return LogLevel.info; + return LogLevelEnum.info; } if (num < 40) { - return LogLevel.warn; + return LogLevelEnum.warn; } - return LogLevel.error; + return LogLevelEnum.error; }; -export const logLevelFromString = enumFromString(LogLevel); - export const logLevelFromExecutionStatus = (status: RuleExecutionStatus): LogLevel => { switch (status) { case RuleExecutionStatusEnum['going to run']: case RuleExecutionStatusEnum.running: case RuleExecutionStatusEnum.succeeded: - return LogLevel.info; + return LogLevelEnum.info; case RuleExecutionStatusEnum['partial failure']: - return LogLevel.warn; + return LogLevelEnum.warn; case RuleExecutionStatusEnum.failed: - return LogLevel.error; + return LogLevelEnum.error; default: assertUnreachable(status); - return LogLevel.trace; + return LogLevelEnum.trace; } }; diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.gen.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.gen.ts index 751e571aae3fdb..352a8cbdf89b00 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.gen.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.gen.ts @@ -6,48 +6,43 @@ */ import { z } from 'zod'; +import { ArrayFromString } from '@kbn/zod-helpers'; /* * NOTICE: Do not edit this file manually. * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. */ -import { RuleExecutionStatus } from '../../model/execution_status.gen'; import { - SortFieldOfRuleExecutionResult, - RuleExecutionResult, -} from '../../model/execution_result.gen'; + RuleExecutionEventType, + LogLevel, + RuleExecutionEvent, +} from '../../model/execution_event.gen'; import { SortOrder } from '../../../model/sorting.gen'; +import { PaginationResult } from '../../../model/pagination.gen'; export type GetRuleExecutionEventsRequestQuery = z.infer; export const GetRuleExecutionEventsRequestQuery = z.object({ /** - * Start date of the time range to query + * Include events of matching the search term. If omitted, all events will be included. */ - start: z.string().datetime(), + search_term: z.string().optional(), /** - * End date of the time range to query + * Include events of the specified types. If omitted, all types of events will be included. */ - end: z.string().datetime(), + event_types: ArrayFromString(RuleExecutionEventType).optional().default([]), /** - * Query text to filter results by + * Include events having these log levels. If omitted, events of all levels will be included. */ - query_text: z.string().optional().default(''), + log_levels: ArrayFromString(LogLevel).optional().default([]), /** - * Comma-separated list of rule execution statuses to filter results by + * Start date of the time range to query */ - status_filters: z - .preprocess( - (value: unknown) => - typeof value === 'string' ? (value === '' ? [] : value.split(',')) : value, - z.array(RuleExecutionStatus) - ) - .optional() - .default([]), + date_start: z.string().datetime().optional(), /** - * Field to sort results by + * End date of the time range to query */ - sort_field: SortFieldOfRuleExecutionResult.optional().default('timestamp'), + date_end: z.string().datetime().optional(), /** * Sort order to sort results by */ @@ -69,9 +64,6 @@ export type GetRuleExecutionEventsRequestParams = z.infer< typeof GetRuleExecutionEventsRequestParams >; export const GetRuleExecutionEventsRequestParams = z.object({ - /** - * Saved object ID of the rule to get execution results for - */ ruleId: z.string().min(1), }); export type GetRuleExecutionEventsRequestParamsInput = z.input< @@ -80,6 +72,6 @@ export type GetRuleExecutionEventsRequestParamsInput = z.input< export type GetRuleExecutionEventsResponse = z.infer; export const GetRuleExecutionEventsResponse = z.object({ - events: z.array(RuleExecutionResult).optional(), - total: z.number().int().optional(), + events: z.array(RuleExecutionEvent), + pagination: PaginationResult, }); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.mock.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.mock.ts index b46f8d9b138705..e730350215f8eb 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.mock.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.mock.ts @@ -6,7 +6,7 @@ */ import { ruleExecutionEventMock } from '../../model/execution_event.mock'; -import type { GetRuleExecutionEventsResponse } from './get_rule_execution_events_route'; +import type { GetRuleExecutionEventsResponse } from './get_rule_execution_events_route.gen'; const getSomeResponse = (): GetRuleExecutionEventsResponse => { const events = ruleExecutionEventMock.getSomeEvents(); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.schema.yaml b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.schema.yaml index 677213bae4f2ee..990ea4ef648765 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.schema.yaml @@ -14,47 +14,47 @@ paths: - name: ruleId in: path required: true - description: Saved object ID of the rule to get execution results for schema: type: string minLength: 1 - - name: start + - name: search_term in: query - required: true - description: Start date of the time range to query - schema: - type: string - format: date-time - - name: end - in: query - required: true - description: End date of the time range to query + required: false + description: Include events of matching the search term. If omitted, all events will be included. schema: type: string - format: date-time - - name: query_text + - name: event_types in: query required: false - description: Query text to filter results by + description: Include events of the specified types. If omitted, all types of events will be included. schema: - type: string - default: '' - - name: status_filters + type: array + items: + $ref: '../../model/execution_event.schema.yaml#/components/schemas/RuleExecutionEventType' + default: [] + - name: log_levels in: query required: false - description: Comma-separated list of rule execution statuses to filter results by + description: Include events having these log levels. If omitted, events of all levels will be included. schema: type: array items: - $ref: '../../model/execution_status.schema.yaml#/components/schemas/RuleExecutionStatus' + $ref: '../../model/execution_event.schema.yaml#/components/schemas/LogLevel' default: [] - - name: sort_field + - name: date_start in: query required: false - description: Field to sort results by + description: Start date of the time range to query schema: - $ref: '../../model/execution_result.schema.yaml#/components/schemas/SortFieldOfRuleExecutionResult' - default: timestamp + type: string + format: date-time + - name: date_end + in: query + required: false + description: End date of the time range to query + schema: + type: string + format: date-time - name: sort_order in: query required: false @@ -87,6 +87,9 @@ paths: events: type: array items: - $ref: '../../model/execution_result.schema.yaml#/components/schemas/RuleExecutionResult' - total: - type: integer + $ref: '../../model/execution_event.schema.yaml#/components/schemas/RuleExecutionEvent' + pagination: + $ref: '../../../model/pagination.schema.yaml#/components/schemas/PaginationResult' + required: + - events + - pagination diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.test.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.test.ts index ecf94032039acf..5f73b6109e8207 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.test.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.test.ts @@ -5,14 +5,11 @@ * 2.0. */ -import { pipe } from 'fp-ts/lib/pipeable'; -import { left } from 'fp-ts/lib/Either'; -import { foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; - +import { expectParseError, expectParseSuccess } from '@kbn/zod-helpers'; import { GetRuleExecutionEventsRequestParams, GetRuleExecutionEventsRequestQuery, -} from './get_rule_execution_events_route'; +} from './get_rule_execution_events_route.gen'; describe('Request schema of Get rule execution events', () => { describe('GetRuleExecutionEventsRequestParams', () => { @@ -22,11 +19,10 @@ describe('Request schema of Get rule execution events', () => { ruleId: 'some id', }; - const decoded = GetRuleExecutionEventsRequestParams.decode(input); - const message = pipe(decoded, foldLeftRight); + const results = GetRuleExecutionEventsRequestParams.safeParse(input); + expectParseSuccess(results); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual( + expect(results.data).toEqual( expect.objectContaining({ ruleId: 'some id', }) @@ -39,23 +35,21 @@ describe('Request schema of Get rule execution events', () => { foo: 'bar', // this one is not in the schema and will be stripped }; - const decoded = GetRuleExecutionEventsRequestParams.decode(input); - const message = pipe(decoded, foldLeftRight); + const results = GetRuleExecutionEventsRequestParams.safeParse(input); + expectParseSuccess(results); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual({ - ruleId: 'some id', - }); + expect(results.data).toEqual( + expect.objectContaining({ + ruleId: 'some id', + }) + ); }); }); describe('Validation fails', () => { const test = (input: unknown) => { - const decoded = GetRuleExecutionEventsRequestParams.decode(input); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors)).length).toBeGreaterThan(0); - expect(message.schema).toEqual({}); + const results = GetRuleExecutionEventsRequestParams.safeParse(input); + expectParseError(results); }; it('when not all the required parameters are passed', () => { @@ -84,11 +78,10 @@ describe('Request schema of Get rule execution events', () => { per_page: 6, }; - const decoded = GetRuleExecutionEventsRequestQuery.decode(input); - const message = pipe(decoded, foldLeftRight); + const result = GetRuleExecutionEventsRequestQuery.safeParse(input); + expectParseSuccess(result); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual({ + expect(result.data).toEqual({ event_types: ['message', 'status-change'], log_levels: ['debug', 'info', 'error'], sort_order: 'asc', @@ -107,11 +100,10 @@ describe('Request schema of Get rule execution events', () => { foo: 'bar', // this one is not in the schema and will be stripped }; - const decoded = GetRuleExecutionEventsRequestQuery.decode(input); - const message = pipe(decoded, foldLeftRight); + const result = GetRuleExecutionEventsRequestQuery.safeParse(input); + expectParseSuccess(result); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual({ + expect(result.data).toEqual({ event_types: ['message', 'status-change'], log_levels: ['debug', 'info', 'error'], sort_order: 'asc', @@ -119,25 +111,12 @@ describe('Request schema of Get rule execution events', () => { per_page: 6, }); }); - - it('when no parameters are passed (all are have default values)', () => { - const input = {}; - - const decoded = GetRuleExecutionEventsRequestQuery.decode(input); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(expect.any(Object)); - }); }); describe('Validation fails', () => { const test = (input: unknown) => { - const decoded = GetRuleExecutionEventsRequestQuery.decode(input); - const message = pipe(decoded, foldLeftRight); - - expect(getPaths(left(message.errors)).length).toBeGreaterThan(0); - expect(message.schema).toEqual({}); + const result = GetRuleExecutionEventsRequestQuery.safeParse(input); + expectParseError(result); }; it('when invalid parameters are passed', () => { @@ -147,21 +126,18 @@ describe('Request schema of Get rule execution events', () => { }); }); - describe('Validation sets default values', () => { - it('when optional parameters are not passed', () => { - const input = {}; + it('Validation sets default values when optional parameters are not passed', () => { + const input = {}; - const decoded = GetRuleExecutionEventsRequestQuery.decode(input); - const message = pipe(decoded, foldLeftRight); + const result = GetRuleExecutionEventsRequestQuery.safeParse(input); + expectParseSuccess(result); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual({ - event_types: [], - log_levels: [], - sort_order: 'desc', - page: 1, - per_page: 20, - }); + expect(result.data).toEqual({ + event_types: [], + log_levels: [], + sort_order: 'desc', + page: 1, + per_page: 20, }); }); }); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.ts deleted file mode 100644 index 628e71cf51790b..00000000000000 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.ts +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as t from 'io-ts'; - -import { DefaultPerPage, DefaultPage } from '@kbn/securitysolution-io-ts-alerting-types'; -import { defaultCsvArray, IsoDateString, NonEmptyString } from '@kbn/securitysolution-io-ts-types'; - -import { DefaultSortOrderDesc, PaginationResult } from '../../../model'; -import { RuleExecutionEvent, TRuleExecutionEventType, TLogLevel } from '../../model'; - -/** - * URL path parameters of the API route. - */ -export type GetRuleExecutionEventsRequestParams = t.TypeOf< - typeof GetRuleExecutionEventsRequestParams ->; -export const GetRuleExecutionEventsRequestParams = t.exact( - t.type({ - ruleId: NonEmptyString, - }) -); - -/** - * Query string parameters of the API route. - */ -export type GetRuleExecutionEventsRequestQuery = t.TypeOf< - typeof GetRuleExecutionEventsRequestQuery ->; -export const GetRuleExecutionEventsRequestQuery = t.exact( - t.intersection([ - t.partial({ - search_term: NonEmptyString, - event_types: defaultCsvArray(TRuleExecutionEventType), - log_levels: defaultCsvArray(TLogLevel), - date_start: IsoDateString, - date_end: IsoDateString, - }), - t.type({ - sort_order: DefaultSortOrderDesc, // defaults to 'desc' - page: DefaultPage, // defaults to 1 - per_page: DefaultPerPage, // defaults to 20 - }), - ]) -); - -/** - * Response body of the API route. - */ -export type GetRuleExecutionEventsResponse = t.TypeOf; -export const GetRuleExecutionEventsResponse = t.exact( - t.type({ - events: t.array(RuleExecutionEvent), - pagination: PaginationResult, - }) -); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_results/get_rule_execution_results_route.gen.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_results/get_rule_execution_results_route.gen.ts index 442c45f3e8dc96..cb8e2f1d8ffa54 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_results/get_rule_execution_results_route.gen.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_results/get_rule_execution_results_route.gen.ts @@ -6,6 +6,7 @@ */ import { z } from 'zod'; +import { ArrayFromString } from '@kbn/zod-helpers'; /* * NOTICE: Do not edit this file manually. @@ -38,14 +39,7 @@ export const GetRuleExecutionResultsRequestQuery = z.object({ /** * Comma-separated list of rule execution statuses to filter results by */ - status_filters: z - .preprocess( - (value: unknown) => - typeof value === 'string' ? (value === '' ? [] : value.split(',')) : value, - z.array(RuleExecutionStatus) - ) - .optional() - .default([]), + status_filters: ArrayFromString(RuleExecutionStatus).optional().default([]), /** * Field to sort results by */ diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/create_signals_migration/create_signals_migration_route.ts b/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/create_signals_migration/create_signals_migration_route.ts index dd1ecc4208ef79..9089efeb87d050 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/create_signals_migration/create_signals_migration_route.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/create_signals_migration/create_signals_migration_route.ts @@ -8,9 +8,6 @@ import * as t from 'io-ts'; import { PositiveInteger, PositiveIntegerGreaterThanZero } from '@kbn/securitysolution-io-ts-types'; -// TODO https://github.com/elastic/security-team/issues/7491 -// eslint-disable-next-line no-restricted-imports -import { IndexPatternArray } from '../../model/rule_schema_legacy'; export const signalsReindexOptions = t.partial({ requests_per_second: t.number, @@ -23,7 +20,7 @@ export type SignalsReindexOptions = t.TypeOf; export const createSignalsMigrationSchema = t.intersection([ t.exact( t.type({ - index: IndexPatternArray, + index: t.array(t.string), }) ), t.exact(signalsReindexOptions), diff --git a/x-pack/plugins/security_solution/common/api/risk_engine/calculation_route_schema.yml b/x-pack/plugins/security_solution/common/api/risk_engine/calculation_route_schema.yml new file mode 100644 index 00000000000000..c851509c4f64a5 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/risk_engine/calculation_route_schema.yml @@ -0,0 +1,91 @@ +openapi: 3.0.0 + +info: + version: 1.0.0 + title: Risk Scoring API + description: These APIs allow the consumer to manage Entity Risk Scores within Entity Analytics. + +servers: + - url: 'http://{kibana_host}:{port}' + variables: + kibana_host: + default: localhost + port: + default: '5601' + +paths: + /api/risk_scores/calculation: + post: + summary: Trigger calculation of Risk Scores + description: Calculates and persists a segment of Risk Scores, returning details about the calculation. + requestBody: + description: Details about the Risk Scores being calculated + content: + application/json: + schema: + $ref: '#/components/schemas/RiskScoresCalculationRequest' + required: true + responses: + '200': + description: Successful response + content: + application/json: + schema: + $ref: '#/components/schemas/RiskScoresCalculationResponse' + '400': + description: Invalid request + +components: + schemas: + RiskScoresCalculationRequest: + type: object + required: + - data_view_id + - identifier_type + - range + properties: + after_keys: + description: Used to calculate a specific "page" of risk scores. If unspecified, the first "page" of scores is returned. See also the `after_keys` key in a risk scores response. + allOf: + - $ref: 'common.yml#/components/schemas/AfterKeys' + data_view_id: + $ref: 'common.yml#/components/schemas/DataViewId' + description: The identifier of the Kibana data view to be used when generating risk scores. If a data view is not found, the provided ID will be used as the query's index pattern instead. + debug: + description: If set to `true`, the internal ES requests/responses will be logged in Kibana. + type: boolean + filter: + $ref: 'common.yml#/components/schemas/Filter' + description: An elasticsearch DSL filter object. Used to filter the data being scored, which implicitly filters the risk scores calculated. + page_size: + $ref: 'common.yml#/components/schemas/PageSize' + identifier_type: + description: Used to restrict the type of risk scores calculated. + allOf: + - $ref: 'common.yml#/components/schemas/IdentifierType' + range: + $ref: 'common.yml#/components/schemas/DateRange' + description: Defines the time period over which scores will be evaluated. If unspecified, a range of `[now, now-30d]` will be used. + weights: + $ref: 'common.yml#/components/schemas/RiskScoreWeights' + + RiskScoresCalculationResponse: + type: object + required: + - after_keys + - errors + - scores_written + properties: + after_keys: + description: Used to obtain the next "page" of risk scores. See also the `after_keys` key in a risk scores request. If this key is empty, the calculation is complete. + allOf: + - $ref: 'common.yml#/components/schemas/AfterKeys' + errors: + type: array + description: A list of errors encountered during the calculation. + items: + type: string + scores_written: + type: number + format: integer + description: The number of risk scores persisted to elasticsearch. diff --git a/x-pack/plugins/security_solution/common/api/risk_engine/common.yml b/x-pack/plugins/security_solution/common/api/risk_engine/common.yml new file mode 100644 index 00000000000000..ea4bd4bd863212 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/risk_engine/common.yml @@ -0,0 +1,199 @@ +openapi: 3.0.0 + +components: + schemas: + + AfterKeys: + type: object + properties: + host: + type: object + additionalProperties: + type: string + user: + type: object + additionalProperties: + type: string + example: + host: + 'host.name': 'example.host' + user: + 'user.name': 'example_user_name' + + DataViewId: + description: The identifier of the Kibana data view to be used when generating risk scores. + example: security-solution-default + type: string + + Filter: + description: An elasticsearch DSL filter object. Used to filter the risk inputs involved, which implicitly filters the risk scores themselves. + $ref: 'https://cloud.elastic.co/api/v1/api-docs/spec.json#/definitions/QueryContainer' + + PageSize: + description: Specifies how many scores will be involved in a given calculation. Note that this value is per `identifier_type`, i.e. a value of 10 will calculate 10 host scores and 10 user scores, if available. To avoid missed data, keep this value consistent while paginating through scores. + default: 1000 + type: number + + DateRange: + description: Defines the time period on which risk inputs will be filtered. + type: object + required: + - start + - end + properties: + start: + $ref: '#/components/schemas/KibanaDate' + end: + $ref: '#/components/schemas/KibanaDate' + + KibanaDate: + type: string + oneOf: + - format: date + - format: date-time + - format: datemath + example: '2017-07-21T17:32:28Z' + + IdentifierType: + type: string + enum: + - host + - user + + RiskScore: + type: object + required: + - '@timestamp' + - id_field + - id_value + - calculated_level + - calculated_score + - calculated_score_norm + - category_1_score + - category_1_count + - inputs + properties: + '@timestamp': + type: string + format: 'date-time' + example: '2017-07-21T17:32:28Z' + description: The time at which the risk score was calculated. + id_field: + type: string + example: 'host.name' + description: The identifier field defining this risk score. Coupled with `id_value`, uniquely identifies the entity being scored. + id_value: + type: string + example: 'example.host' + description: The identifier value defining this risk score. Coupled with `id_field`, uniquely identifies the entity being scored. + calculated_level: + type: string + example: 'Critical' + description: Lexical description of the entity's risk. + calculated_score: + type: number + format: double + description: The raw numeric value of the given entity's risk score. + calculated_score_norm: + type: number + format: double + minimum: 0 + maximum: 100 + description: The normalized numeric value of the given entity's risk score. Useful for comparing with other entities. + category_1_score: + type: number + format: double + description: The contribution of Category 1 to the overall risk score (`calculated_score`). Category 1 contains Detection Engine Alerts. + category_1_count: + type: number + format: integer + description: The number of risk input documents that contributed to the Category 1 score (`category_1_score`). + inputs: + type: array + description: A list of the highest-risk documents contributing to this risk score. Useful for investigative purposes. + items: + $ref: '#/components/schemas/RiskScoreInput' + + RiskScoreInput: + description: A generic representation of a document contributing to a Risk Score. + type: object + properties: + id: + type: string + example: 91a93376a507e86cfbf282166275b89f9dbdb1f0be6c8103c6ff2909ca8e1a1c + description: The unique identifier (`_id`) of the original source document + index: + type: string + example: .internal.alerts-security.alerts-default-000001 + description: The unique index (`_index`) of the original source document + category: + type: string + example: category_1 + description: The risk category of the risk input document. + description: + type: string + example: 'Generated from Detection Engine Rule: Malware Prevention Alert' + description: A human-readable description of the risk input document. + risk_score: + type: number + format: double + minimum: 0 + maximum: 100 + description: The weighted risk score of the risk input document. + timestamp: + type: string + example: '2017-07-21T17:32:28Z' + description: The @timestamp of the risk input document. + + RiskScoreWeight: + description: "Configuration used to tune risk scoring. Weights can be used to change the score contribution of risk inputs for hosts and users at both a global level and also for Risk Input categories (e.g. 'category_1')." + type: object + required: + - type + properties: + type: + type: string + value: + type: string + host: + type: number + format: double + minimum: 0 + maximum: 1 + user: + type: number + format: double + minimum: 0 + maximum: 1 + example: + type: 'risk_category' + value: 'category_1' + host: 0.8 + user: 0.4 + + RiskScoreWeights: + description: 'A list of weights to be applied to the scoring calculation.' + type: array + items: + $ref: '#/components/schemas/RiskScoreWeight' + example: + - type: 'risk_category' + value: 'category_1' + host: 0.8 + user: 0.4 + - type: 'global_identifier' + host: 0.5 + user: 0.1 + + RiskEngineInitStep: + type: object + required: + - type + - success + properties: + type: + type: string + success: + type: boolean + error: + type: string diff --git a/x-pack/plugins/security_solution/common/api/risk_engine/engine_disable_route_schema.yml b/x-pack/plugins/security_solution/common/api/risk_engine/engine_disable_route_schema.yml new file mode 100644 index 00000000000000..71d4c966818143 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/risk_engine/engine_disable_route_schema.yml @@ -0,0 +1,37 @@ +openapi: 3.0.0 + +info: + version: 1.0.0 + title: Risk Scoring API + description: These APIs allow the consumer to manage Entity Risk Scores within Entity Analytics. + +servers: + - url: 'http://{kibana_host}:{port}' + variables: + kibana_host: + default: localhost + port: + default: '5601' + +paths: + /internal/risk_score/engine/disable: + post: + summary: Disable the Risk Engine + requestBody: + content: + application/json: {} + responses: + '200': + description: Successful response + content: + application/json: + schema: + $ref: '#/components/schemas/RiskEngineDisableResponse' + +components: + schemas: + RiskEngineDisableResponse: + type: object + properties: + success: + type: boolean diff --git a/x-pack/plugins/security_solution/common/api/risk_engine/engine_enable_route_schema.yml b/x-pack/plugins/security_solution/common/api/risk_engine/engine_enable_route_schema.yml new file mode 100644 index 00000000000000..42e4ec7af0a504 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/risk_engine/engine_enable_route_schema.yml @@ -0,0 +1,37 @@ +openapi: 3.0.0 + +info: + version: 1.0.0 + title: Risk Scoring API + description: These APIs allow the consumer to manage Entity Risk Scores within Entity Analytics. + +servers: + - url: 'http://{kibana_host}:{port}' + variables: + kibana_host: + default: localhost + port: + default: '5601' + +paths: + /internal/risk_score/engine/enable: + post: + summary: Enable the Risk Engine + requestBody: + content: + application/json: {} + responses: + '200': + description: Successful response + content: + application/json: + schema: + $ref: '#/components/schemas/RiskEngineEnableResponse' + +components: + schemas: + RiskEngineEnableResponse: + type: object + properties: + success: + type: boolean diff --git a/x-pack/plugins/security_solution/common/api/risk_engine/engine_init_route_schema.yml b/x-pack/plugins/security_solution/common/api/risk_engine/engine_init_route_schema.yml new file mode 100644 index 00000000000000..daa6b3a33bb223 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/risk_engine/engine_init_route_schema.yml @@ -0,0 +1,46 @@ +openapi: 3.0.0 +info: + version: 1.0.0 + title: Risk Scoring API + description: These APIs allow the consumer to manage Entity Risk Scores within Entity Analytics. +servers: + - url: 'http://{kibana_host}:{port}' + variables: + kibana_host: + default: localhost + port: + default: '5601' + +paths: + /internal/risk_score/engine/init: + post: + summary: Initialize the Risk Engine + description: Initializes the Risk Engine by creating the necessary indices and mappings, removing old transforms, and starting the new risk engine + responses: + '200': + description: Successful response + content: + application/json: + schema: + $ref: '#/components/schemas/RiskEngineInitResponse' + +components: + schemas: + RiskEngineInitResponse: + type: object + properties: + result: + type: object + properties: + risk_engine_enabled: + type: boolean + risk_engine_resources_installed: + type: boolean + risk_engine_configuration_created: + type: boolean + legacy_risk_engine_disabled: + type: boolean + errors: + type: array + items: + type: string diff --git a/x-pack/plugins/security_solution/common/api/risk_engine/engine_status_route_schema.yml b/x-pack/plugins/security_solution/common/api/risk_engine/engine_status_route_schema.yml new file mode 100644 index 00000000000000..4aed23d48a0d59 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/risk_engine/engine_status_route_schema.yml @@ -0,0 +1,45 @@ +openapi: 3.0.0 +info: + version: 1.0.0 + title: Risk Scoring API + description: These APIs allow the consumer to manage Entity Risk Scores within Entity Analytics. +servers: + - url: 'http://{kibana_host}:{port}' + variables: + kibana_host: + default: localhost + port: + default: '5601' + +paths: + /engine/status: + get: + summary: Get the status of the Risk Engine + description: Returns the status of both the legacy transform-based risk engine, as well as the new risk engine + responses: + '200': + description: Successful response + content: + application/json: + schema: + $ref: '#/components/schemas/RiskEngineStatusResponse' +components: + schemas: + + RiskEngineStatusResponse: + type: object + properties: + legacy_risk_engine_status: + $ref: '#/components/schemas/RiskEngineStatus' + risk_engine_status: + $ref: '#/components/schemas/RiskEngineStatus' + is_max_amount_of_risk_engines_reached: + description: Indicates whether the maximum amount of risk engines has been reached + type: boolean + + RiskEngineStatus: + type: string + enum: + - 'NOT_INSTALLED' + - 'DISABLED' + - 'ENABLED' diff --git a/x-pack/plugins/security_solution/common/api/risk_engine/preview_route_schema.yml b/x-pack/plugins/security_solution/common/api/risk_engine/preview_route_schema.yml new file mode 100644 index 00000000000000..fc66d3ed882c97 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/risk_engine/preview_route_schema.yml @@ -0,0 +1,90 @@ +openapi: 3.0.0 +info: + version: 1.0.0 + title: Risk Scoring API + description: These APIs allow the consumer to manage Entity Risk Scores within Entity Analytics. +servers: + - url: 'http://{kibana_host}:{port}' + variables: + kibana_host: + default: localhost + port: + default: '5601' + +paths: + /internal/risk_score/preview: + post: + summary: Preview the calculation of Risk Scores + description: Calculates and returns a list of Risk Scores, sorted by identifier_type and risk score. + requestBody: + description: Details about the Risk Scores being requested + content: + application/json: + schema: + $ref: '#/components/schemas/RiskScoresPreviewRequest' + required: true + responses: + '200': + description: Successful response + content: + application/json: + schema: + $ref: '#/components/schemas/RiskScoresPreviewResponse' + '400': + description: Invalid request + +components: + schemas: + RiskScoresPreviewRequest: + type: object + required: + - data_view_id + properties: + after_keys: + description: Used to retrieve a specific "page" of risk scores. If unspecified, the first "page" of scores is returned. See also the `after_keys` key in a risk scores response. + allOf: + - $ref: 'common.yml#/components/schemas/AfterKeys' + data_view_id: + $ref: 'common.yml#/components/schemas/DataViewId' + description: The identifier of the Kibana data view to be used when generating risk scores. If a data view is not found, the provided ID will be used as the query's index pattern instead. + debug: + description: If set to `true`, a `debug` key is added to the response, containing both the internal request and response with elasticsearch. + type: boolean + filter: + $ref: 'common.yml#/components/schemas/Filter' + description: An elasticsearch DSL filter object. Used to filter the data being scored, which implicitly filters the risk scores returned. + page_size: + $ref: 'common.yml#/components/schemas/PageSize' + identifier_type: + description: Used to restrict the type of risk scores involved. If unspecified, both `host` and `user` scores will be returned. + allOf: + - $ref: 'common.yml#/components/schemas/IdentifierType' + range: + $ref: 'common.yml#/components/schemas/DateRange' + description: Defines the time period over which scores will be evaluated. If unspecified, a range of `[now, now-30d]` will be used. + weights: + $ref: 'common.yml#/components/schemas/RiskScoreWeights' + + RiskScoresPreviewResponse: + type: object + required: + - after_keys + - scores + properties: + after_keys: + description: Used to obtain the next "page" of risk scores. See also the `after_keys` key in a risk scores request. If this key is empty, the calculation is complete. + allOf: + - $ref: 'common.yml#/components/schemas/AfterKeys' + debug: + description: Object containing debug information, particularly the internal request and response from elasticsearch + type: object + properties: + request: + type: string + response: + type: string + scores: + type: array + description: A list of risk scores + items: + $ref: 'common.yml#/components/schemas/RiskScore' diff --git a/x-pack/plugins/security_solution/common/api/timeline/model/api.ts b/x-pack/plugins/security_solution/common/api/timeline/model/api.ts index 864b4613857e22..c423b2a4418bba 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/model/api.ts +++ b/x-pack/plugins/security_solution/common/api/timeline/model/api.ts @@ -21,7 +21,7 @@ import { SavedObjectResolveAliasTargetId, SavedObjectResolveOutcome, } from '../../detection_engine/model/rule_schema_legacy'; -import { ErrorSchema, success, success_count as successCount } from '../../detection_engine'; +import { ErrorSchema } from './error_schema'; export const BareNoteSchema = runtimeTypes.intersection([ runtimeTypes.type({ @@ -497,8 +497,8 @@ export interface ExportTimelineNotFoundError { export const importTimelineResultSchema = runtimeTypes.exact( runtimeTypes.type({ - success, - success_count: successCount, + success: runtimeTypes.boolean, + success_count: PositiveInteger, timelines_installed: PositiveInteger, timelines_updated: PositiveInteger, errors: runtimeTypes.array(ErrorSchema), diff --git a/x-pack/plugins/security_solution/common/api/timeline/model/error_schema.mock.ts b/x-pack/plugins/security_solution/common/api/timeline/model/error_schema.mock.ts new file mode 100644 index 00000000000000..7c24d605d5f981 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/timeline/model/error_schema.mock.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { ErrorSchema } from './error_schema'; + +export const getErrorSchemaMock = ( + id: string = '819eded6-e9c8-445b-a647-519aea39e063' +): ErrorSchema => ({ + id, + error: { + status_code: 404, + message: 'id: "819eded6-e9c8-445b-a647-519aea39e063" not found', + }, +}); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/error_schema.test.ts b/x-pack/plugins/security_solution/common/api/timeline/model/error_schema.test.ts similarity index 93% rename from x-pack/plugins/security_solution/common/api/detection_engine/model/error_schema.test.ts rename to x-pack/plugins/security_solution/common/api/timeline/model/error_schema.test.ts index 164f5ee854efc3..8326479db9c14c 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/model/error_schema.test.ts +++ b/x-pack/plugins/security_solution/common/api/timeline/model/error_schema.test.ts @@ -8,9 +8,7 @@ import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; -// TODO https://github.com/elastic/security-team/issues/7491 -// eslint-disable-next-line no-restricted-imports -import { ErrorSchema } from './error_schema_legacy'; +import { ErrorSchema } from './error_schema'; import { getErrorSchemaMock } from './error_schema.mock'; describe('error_schema', () => { diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/error_schema_legacy.ts b/x-pack/plugins/security_solution/common/api/timeline/model/error_schema.ts similarity index 69% rename from x-pack/plugins/security_solution/common/api/detection_engine/model/error_schema_legacy.ts rename to x-pack/plugins/security_solution/common/api/timeline/model/error_schema.ts index c2efee05269c15..a0ac17765c3a80 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/model/error_schema_legacy.ts +++ b/x-pack/plugins/security_solution/common/api/timeline/model/error_schema.ts @@ -5,22 +5,16 @@ * 2.0. */ -import { NonEmptyString } from '@kbn/securitysolution-io-ts-types'; +import { NonEmptyString, PositiveInteger } from '@kbn/securitysolution-io-ts-types'; import * as t from 'io-ts'; -// TODO https://github.com/elastic/security-team/issues/7491 -// eslint-disable-next-line no-restricted-imports -import { RuleSignatureId } from './rule_schema_legacy'; - -import { status_code, message } from './schemas'; - // We use id: t.string intentionally and _never_ the id from global schemas as // sometimes echo back out the id that the user gave us and it is not guaranteed // to be a UUID but rather just a string const partial = t.exact( t.partial({ id: t.string, - rule_id: RuleSignatureId, + rule_id: NonEmptyString, list_id: NonEmptyString, item_id: NonEmptyString, }) @@ -28,8 +22,8 @@ const partial = t.exact( const required = t.exact( t.type({ error: t.type({ - status_code, - message, + status_code: PositiveInteger, + message: t.string, }), }) ); diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index c6edaf898f67ff..a7460bcd703458 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -505,3 +505,8 @@ export const DEFAULT_ALERT_TAGS_VALUE = [ i18n.FALSE_POSITIVE, i18n.FURTHER_INVESTIGATION_REQUIRED, ] as const; + +/** + * Max length for the comments within security solution + */ +export const MAX_COMMENT_LENGTH = 30000 as const; diff --git a/x-pack/plugins/security_solution/common/detection_engine/transform_actions.test.ts b/x-pack/plugins/security_solution/common/detection_engine/transform_actions.test.ts index 6bdce7573ed4c7..1e2db97225580c 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/transform_actions.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/transform_actions.test.ts @@ -17,8 +17,8 @@ import type { ResponseAction, RuleResponseAction, } from '../api/detection_engine/model/rule_response_actions'; -import { RESPONSE_ACTION_TYPES } from '../api/detection_engine/model/rule_response_actions'; -import type { NormalizedRuleAction } from '../api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import { ResponseActionTypesEnum } from '../api/detection_engine/model/rule_response_actions'; +import type { NormalizedRuleAction } from '../api/detection_engine/rule_management'; import type { RuleAction } from '@kbn/alerting-plugin/common'; describe('transform_actions', () => { @@ -93,7 +93,7 @@ describe('transform_actions', () => { }); test('it should transform ResponseAction[] to RuleResponseAction[]', () => { const ruleAction: ResponseAction = { - action_type_id: RESPONSE_ACTION_TYPES.OSQUERY, + action_type_id: ResponseActionTypesEnum['.osquery'], params: { ecs_mapping: {}, saved_query_id: undefined, @@ -117,7 +117,7 @@ describe('transform_actions', () => { test('it should transform RuleResponseAction[] to ResponseAction[]', () => { const alertAction: RuleResponseAction = { - actionTypeId: RESPONSE_ACTION_TYPES.OSQUERY, + actionTypeId: ResponseActionTypesEnum['.osquery'], params: { ecsMapping: {}, savedQueryId: undefined, diff --git a/x-pack/plugins/security_solution/common/detection_engine/transform_actions.ts b/x-pack/plugins/security_solution/common/detection_engine/transform_actions.ts index 5750f35d893e20..1f3727e6e4e0be 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/transform_actions.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/transform_actions.ts @@ -7,12 +7,12 @@ import type { RuleAction as AlertingRuleAction } from '@kbn/alerting-plugin/common'; import type { NormalizedAlertAction } from '@kbn/alerting-plugin/server/rules_client'; -import type { NormalizedRuleAction } from '../api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import type { NormalizedRuleAction } from '../api/detection_engine/rule_management'; import type { ResponseAction, RuleResponseAction, } from '../api/detection_engine/model/rule_response_actions'; -import { RESPONSE_ACTION_TYPES } from '../api/detection_engine/model/rule_response_actions'; +import { ResponseActionTypesEnum } from '../api/detection_engine/model/rule_response_actions'; import type { RuleAction } from '../api/detection_engine/model'; export const transformRuleToAlertAction = ({ @@ -63,7 +63,12 @@ export const transformNormalizedRuleToAlertAction = ({ group, id, params: params as AlertingRuleAction['params'], - ...(alertsFilter && { alertsFilter }), + ...(alertsFilter && { + // We use "unknown" as the alerts filter type which is stricter than the one + // used in the alerting plugin (what they use is essentially "any"). So we + // have to to cast here + alertsFilter: alertsFilter as AlertingRuleAction['alertsFilter'], + }), ...(frequency && { frequency }), }); @@ -85,7 +90,7 @@ export const transformRuleToAlertResponseAction = ({ action_type_id: actionTypeId, params, }: ResponseAction): RuleResponseAction => { - if (actionTypeId === RESPONSE_ACTION_TYPES.OSQUERY) { + if (actionTypeId === ResponseActionTypesEnum['.osquery']) { const { saved_query_id: savedQueryId, ecs_mapping: ecsMapping, @@ -113,7 +118,7 @@ export const transformAlertToRuleResponseAction = ({ actionTypeId, params, }: RuleResponseAction): ResponseAction => { - if (actionTypeId === RESPONSE_ACTION_TYPES.OSQUERY) { + if (actionTypeId === ResponseActionTypesEnum['.osquery']) { const { savedQueryId, ecsMapping, packId, ...rest } = params; return { params: { diff --git a/x-pack/plugins/security_solution/common/risk_engine/risk_score_calculation/request_schema.ts b/x-pack/plugins/security_solution/common/risk_engine/risk_score_calculation/request_schema.ts index 6058f60e1e1c6d..c05ca782afaceb 100644 --- a/x-pack/plugins/security_solution/common/risk_engine/risk_score_calculation/request_schema.ts +++ b/x-pack/plugins/security_solution/common/risk_engine/risk_score_calculation/request_schema.ts @@ -6,9 +6,6 @@ */ import * as t from 'io-ts'; -// TODO https://github.com/elastic/security-team/issues/7491 -// eslint-disable-next-line no-restricted-imports -import { DataViewId } from '../../api/detection_engine/model/rule_schema_legacy'; import { afterKeysSchema } from '../after_keys'; import { identifierTypeSchema } from '../identifier_types'; import { riskWeightsSchema } from '../risk_weights/schema'; @@ -16,7 +13,7 @@ import { riskWeightsSchema } from '../risk_weights/schema'; export const riskScoreCalculationRequestSchema = t.exact( t.intersection([ t.type({ - data_view_id: DataViewId, + data_view_id: t.string, identifier_type: identifierTypeSchema, range: t.type({ start: t.string, diff --git a/x-pack/plugins/security_solution/common/risk_engine/risk_score_preview/request_schema.ts b/x-pack/plugins/security_solution/common/risk_engine/risk_score_preview/request_schema.ts index c4402483116365..76ee6a303532b2 100644 --- a/x-pack/plugins/security_solution/common/risk_engine/risk_score_preview/request_schema.ts +++ b/x-pack/plugins/security_solution/common/risk_engine/risk_score_preview/request_schema.ts @@ -6,9 +6,6 @@ */ import * as t from 'io-ts'; -// TODO https://github.com/elastic/security-team/issues/7491 -// eslint-disable-next-line no-restricted-imports -import { DataViewId } from '../../api/detection_engine/model/rule_schema_legacy'; import { afterKeysSchema } from '../after_keys'; import { identifierTypeSchema } from '../identifier_types'; import { rangeSchema } from '../range'; @@ -17,7 +14,7 @@ import { riskWeightsSchema } from '../risk_weights/schema'; export const riskScorePreviewRequestSchema = t.exact( t.intersection([ t.type({ - data_view_id: DataViewId, + data_view_id: t.string, }), t.partial({ after_keys: afterKeysSchema, diff --git a/x-pack/plugins/security_solution/common/types/response_actions/index.ts b/x-pack/plugins/security_solution/common/types/response_actions/index.ts index 07124b6bc5e45b..35333bdc54eb75 100644 --- a/x-pack/plugins/security_solution/common/types/response_actions/index.ts +++ b/x-pack/plugins/security_solution/common/types/response_actions/index.ts @@ -13,7 +13,7 @@ export interface RawEventData { _index: string; } -export enum RESPONSE_ACTION_TYPES { +export enum ResponseActionTypesEnum { OSQUERY = '.osquery', ENDPOINT = '.endpoint', } @@ -34,7 +34,7 @@ export interface ExpandedEventFieldsObject { type RuleParameters = Array<{ response_actions: Array<{ - action_type_id: RESPONSE_ACTION_TYPES; + action_type_id: ResponseActionTypesEnum; params: Record; }>; }>; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx index 7323c293cd94ce..edc72e92ff1531 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { waitFor } from '@testing-library/dom'; +import { waitFor } from '@testing-library/react'; import { mount } from 'enzyme'; import type { ReactWrapper } from 'enzyme'; import React from 'react'; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/osquery_tab.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/osquery_tab.tsx index 289561c0bc4aa5..274c649ece9dcf 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/osquery_tab.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/osquery_tab.tsx @@ -20,7 +20,7 @@ import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_fe import { useKibana } from '../../lib/kibana'; import { EventsViewType } from './event_details'; import * as i18n from './translations'; -import { RESPONSE_ACTION_TYPES } from '../../../../common/api/detection_engine/model/rule_response_actions'; +import { ResponseActionTypesEnum } from '../../../../common/api/detection_engine/model/rule_response_actions'; const TabContentWrapper = styled.div` height: 100%; @@ -71,7 +71,7 @@ export const useOsqueryTab = ({ } const osqueryResponseActions = responseActions.filter( - (responseAction) => responseAction.action_type_id === RESPONSE_ACTION_TYPES.OSQUERY + (responseAction) => responseAction.action_type_id === ResponseActionTypesEnum['.osquery'] ); if (!osqueryResponseActions?.length) { diff --git a/x-pack/plugins/security_solution/public/common/components/ml/score/anomaly_score.test.tsx b/x-pack/plugins/security_solution/public/common/components/ml/score/anomaly_score.test.tsx index 4bdbbdca8bdcad..d8b43fa9a603b8 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml/score/anomaly_score.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/ml/score/anomaly_score.test.tsx @@ -15,7 +15,7 @@ import { mockAnomalies } from '../mock'; import { TestProviders } from '../../../mock/test_providers'; import { useMountAppended } from '../../../utils/use_mount_appended'; import type { Anomalies } from '../types'; -import { waitFor } from '@testing-library/dom'; +import { waitFor } from '@testing-library/react'; jest.mock('../../../lib/kibana'); diff --git a/x-pack/plugins/security_solution/public/common/components/ml/score/anomaly_scores.test.tsx b/x-pack/plugins/security_solution/public/common/components/ml/score/anomaly_scores.test.tsx index 6a72d72a9eb595..b54ec85bab0e44 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml/score/anomaly_scores.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/ml/score/anomaly_scores.test.tsx @@ -16,7 +16,7 @@ import { TestProviders } from '../../../mock/test_providers'; import { getEmptyValue } from '../../empty_value'; import type { Anomalies } from '../types'; import { useMountAppended } from '../../../utils/use_mount_appended'; -import { waitFor } from '@testing-library/dom'; +import { waitFor } from '@testing-library/react'; jest.mock('../../../lib/kibana'); diff --git a/x-pack/plugins/security_solution/public/common/components/ml/score/create_descriptions_list.test.tsx b/x-pack/plugins/security_solution/public/common/components/ml/score/create_descriptions_list.test.tsx index c9de8aad04de82..832c60c69be87a 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml/score/create_descriptions_list.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/ml/score/create_descriptions_list.test.tsx @@ -11,7 +11,7 @@ import { mockAnomalies } from '../mock'; import { createDescriptionList } from './create_description_list'; import { EuiDescriptionList } from '@elastic/eui'; import type { Anomaly } from '../types'; -import { waitFor } from '@testing-library/dom'; +import { waitFor } from '@testing-library/react'; jest.mock('../../../lib/kibana'); diff --git a/x-pack/plugins/security_solution/public/common/components/popover_items/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/popover_items/index.test.tsx index 8d7123bfe6d66c..58584f4325d4e2 100644 --- a/x-pack/plugins/security_solution/public/common/components/popover_items/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/popover_items/index.test.tsx @@ -10,8 +10,7 @@ import React from 'react'; import type { PopoverItemsProps } from '.'; import { PopoverItems } from '.'; import { TestProviders } from '../../mock'; -import { render, screen } from '@testing-library/react'; -import { within } from '@testing-library/dom'; +import { render, screen, within } from '@testing-library/react'; import { waitForEuiPopoverOpen } from '@elastic/eui/lib/test/rtl'; const mockTags = ['Elastic', 'Endpoint', 'Data Protection', 'ML', 'Continuous Monitoring']; diff --git a/x-pack/plugins/security_solution/public/common/components/sourcerer/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/sourcerer/index.test.tsx index ceab08373993a6..ebda7e6748ebda 100644 --- a/x-pack/plugins/security_solution/public/common/components/sourcerer/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/sourcerer/index.test.tsx @@ -22,7 +22,7 @@ import { } from '../../mock'; import { createStore } from '../../store'; import type { EuiSuperSelectOption } from '@elastic/eui/src/components/form/super_select/super_select_control'; -import { waitFor } from '@testing-library/dom'; +import { waitFor } from '@testing-library/react'; import { useSourcererDataView } from '../../containers/sourcerer'; import { useSignalHelpers } from '../../containers/sourcerer/use_signal_helpers'; import { TimelineId } from '../../../../common/types/timeline'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/index.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/index.test.tsx index 573419676a0866..63b0490b91e606 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/index.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import type { ReactWrapper } from 'enzyme'; import { mount, shallow } from 'enzyme'; -import { waitFor, render } from '@testing-library/react'; +import { act, fireEvent, render, waitFor } from '@testing-library/react'; import { getExceptionListSchemaMock } from '@kbn/lists-plugin/common/schemas/response/exception_list_schema.mock'; import { getExceptionBuilderComponentLazy } from '@kbn/lists-plugin/public'; @@ -35,6 +35,7 @@ import { import type { AlertData } from '../../utils/types'; import { useFindRules } from '../../../rule_management/logic/use_find_rules'; import { useFindExceptionListReferences } from '../../logic/use_find_references'; +import { MAX_COMMENT_LENGTH } from '../../../../../common/constants'; jest.mock('../../../../detections/containers/detection_engine/alerts/use_signal_index'); jest.mock('../../../../common/lib/kibana'); @@ -1305,5 +1306,46 @@ describe('When the add exception modal is opened', () => { wrapper.find('button[data-test-subj="addExceptionConfirmButton"]').getDOMNode() ).toBeDisabled(); }); + + test('when there is a comment error has submit button disabled', async () => { + const { getByLabelText, queryByText, getByTestId } = render( + + + + ); + + const commentInput = getByLabelText('Comment Input'); + + const commentErrorMessage = `The length of the comment is too long. The maximum length is ${MAX_COMMENT_LENGTH} characters.`; + expect(queryByText(commentErrorMessage)).toBeNull(); + + // Put comment with the length above maximum allowed + act(() => { + fireEvent.change(commentInput, { + target: { + value: [...new Array(MAX_COMMENT_LENGTH + 1).keys()].map((_) => 'a').join(''), + }, + }); + fireEvent.blur(commentInput); + }); + expect(queryByText(commentErrorMessage)).not.toBeNull(); + expect(getByTestId('addExceptionConfirmButton')).toBeDisabled(); + }); }); }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/index.tsx index d4108c3eddede9..9eefb96be62c9d 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/index.tsx @@ -157,6 +157,7 @@ export const AddExceptionFlyout = memo(function AddExceptionFlyout({ selectedRulesToAddTo, exceptionListsToAddTo, newComment, + commentErrorExists, itemConditionValidationErrorExists, errorSubmitting, expireTime, @@ -267,6 +268,16 @@ export const AddExceptionFlyout = memo(function AddExceptionFlyout({ [dispatch] ); + const setCommentError = useCallback( + (errorExists: boolean): void => { + dispatch({ + type: 'setCommentError', + errorExists, + }); + }, + [dispatch] + ); + const setBulkCloseIndex = useCallback( (index: string[] | undefined): void => { dispatch({ @@ -445,6 +456,7 @@ export const AddExceptionFlyout = memo(function AddExceptionFlyout({ exceptionItemName.trim() === '' || exceptionItems.every((item) => item.entries.length === 0) || itemConditionValidationErrorExists || + commentErrorExists || expireErrorExists || (addExceptionToRadioSelection === 'add_to_lists' && isEmpty(exceptionListsToAddTo)) || (addExceptionToRadioSelection === 'select_rules_to_add_to' && @@ -462,6 +474,7 @@ export const AddExceptionFlyout = memo(function AddExceptionFlyout({ expireErrorExists, selectedRulesToAddTo, listType, + commentErrorExists, ] ); @@ -555,6 +568,7 @@ export const AddExceptionFlyout = memo(function AddExceptionFlyout({ initialIsOpen={!!newComment} newCommentValue={newComment} newCommentOnChange={setComment} + setCommentError={setCommentError} /> {listType !== ExceptionListTypeEnum.ENDPOINT && ( <> diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/reducer.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/reducer.ts index 04d13c3a1b4e99..ec8040d1fe7ccb 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/reducer.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/reducer.ts @@ -21,6 +21,7 @@ export interface State { initialItems: ExceptionsBuilderExceptionItem[]; exceptionItems: ExceptionsBuilderReturnExceptionItem[]; newComment: string; + commentErrorExists: boolean; addExceptionToRadioSelection: string; itemConditionValidationErrorExists: boolean; closeSingleAlert: boolean; @@ -40,6 +41,7 @@ export const initialState: State = { exceptionItems: [], exceptionItemMeta: { name: '' }, newComment: '', + commentErrorExists: false, itemConditionValidationErrorExists: false, closeSingleAlert: false, bulkCloseAlerts: false, @@ -76,6 +78,10 @@ export type Action = type: 'setComment'; comment: string; } + | { + type: 'setCommentError'; + errorExists: boolean; + } | { type: 'setCloseSingleAlert'; close: boolean; @@ -127,6 +133,7 @@ export type Action = export const createExceptionItemsReducer = () => + /* eslint complexity: ["error", 21]*/ (state: State, action: Action): State => { switch (action.type) { case 'setExceptionItemMeta': { @@ -172,6 +179,14 @@ export const createExceptionItemsReducer = newComment: comment, }; } + case 'setCommentError': { + const { errorExists } = action; + + return { + ...state, + commentErrorExists: errorExists, + }; + } case 'setCloseSingleAlert': { const { close } = action; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/index.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/index.test.tsx index faa7c1385142c8..077befdad52ba2 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/index.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { waitFor } from '@testing-library/react'; +import { act, fireEvent, render, waitFor } from '@testing-library/react'; import { ThemeProvider } from 'styled-components'; import type { ReactWrapper } from 'enzyme'; import { mount } from 'enzyme'; @@ -34,6 +34,7 @@ import { useFetchIndexPatterns } from '../../logic/use_exception_flyout_data'; import { useCreateOrUpdateException } from '../../logic/use_create_update_exception'; import { useFindExceptionListReferences } from '../../logic/use_find_references'; import * as i18n from './translations'; +import { MAX_COMMENT_LENGTH } from '../../../../../common/constants'; const mockTheme = getMockTheme({ eui: { @@ -693,5 +694,60 @@ describe('When the edit exception modal is opened', () => { wrapper.find('button[data-test-subj="editExceptionConfirmButton"]').getDOMNode() ).toBeDisabled(); }); + + test('when there is a comment error has submit button disabled', async () => { + const { getByLabelText, queryByText, getByTestId } = render( + + + + ); + + const commentInput = getByLabelText('Comment Input'); + + const commentErrorMessage = `The length of the comment is too long. The maximum length is ${MAX_COMMENT_LENGTH} characters.`; + expect(queryByText(commentErrorMessage)).toBeNull(); + + // Put comment with the length above maximum allowed + act(() => { + fireEvent.change(commentInput, { + target: { + value: [...new Array(MAX_COMMENT_LENGTH + 1).keys()].map((_) => 'a').join(''), + }, + }); + fireEvent.blur(commentInput); + }); + expect(queryByText(commentErrorMessage)).not.toBeNull(); + expect(getByTestId('editExceptionConfirmButton')).toBeDisabled(); + }); }); }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/index.tsx index 4d9e7c3bbc4efc..6d2526cdbf239b 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/index.tsx @@ -117,6 +117,7 @@ const EditExceptionFlyoutComponent: React.FC = ({ exceptionItems, exceptionItemMeta: { name: exceptionItemName }, newComment, + commentErrorExists, bulkCloseAlerts, disableBulkClose, bulkCloseIndex, @@ -129,6 +130,7 @@ const EditExceptionFlyoutComponent: React.FC = ({ exceptionItems: [itemToEdit], exceptionItemMeta: { name: itemToEdit.name }, newComment: '', + commentErrorExists: false, bulkCloseAlerts: false, disableBulkClose: true, bulkCloseIndex: undefined, @@ -197,6 +199,16 @@ const EditExceptionFlyoutComponent: React.FC = ({ [dispatch] ); + const setCommentError = useCallback( + (errorExists: boolean): void => { + dispatch({ + type: 'setCommentError', + errorExists, + }); + }, + [dispatch] + ); + const setBulkCloseAlerts = useCallback( (bulkClose: boolean): void => { dispatch({ @@ -337,8 +349,17 @@ const EditExceptionFlyoutComponent: React.FC = ({ exceptionItems.every((item) => item.entries.length === 0) || isLoading || entryErrorExists || + expireErrorExists || + commentErrorExists, + [ + isLoading, + entryErrorExists, + exceptionItems, + isSubmitting, + isClosingAlerts, expireErrorExists, - [isLoading, entryErrorExists, exceptionItems, isSubmitting, isClosingAlerts, expireErrorExists] + commentErrorExists, + ] ); return ( @@ -398,6 +419,7 @@ const EditExceptionFlyoutComponent: React.FC = ({ exceptionItemComments={itemToEdit.comments} newCommentValue={newComment} newCommentOnChange={setComment} + setCommentError={setCommentError} /> {listType !== ExceptionListTypeEnum.ENDPOINT && ( <> diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/reducer.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/reducer.ts index e08b3c8d135c07..e6dee3af16572e 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/reducer.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/reducer.ts @@ -12,6 +12,7 @@ export interface State { exceptionItems: ExceptionsBuilderReturnExceptionItem[]; exceptionItemMeta: { name: string }; newComment: string; + commentErrorExists: boolean; bulkCloseAlerts: boolean; disableBulkClose: boolean; bulkCloseIndex: string[] | undefined; @@ -29,6 +30,10 @@ export type Action = type: 'setComment'; comment: string; } + | { + type: 'setCommentError'; + errorExists: boolean; + } | { type: 'setBulkCloseAlerts'; bulkClose: boolean; @@ -81,6 +86,14 @@ export const createExceptionItemsReducer = newComment: comment, }; } + case 'setCommentError': { + const { errorExists } = action; + + return { + ...state, + commentErrorExists: errorExists, + }; + } case 'setBulkCloseAlerts': { const { bulkClose } = action; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_to_rules_table/use_add_to_rules_table.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_to_rules_table/use_add_to_rules_table.test.tsx index 7afa5e5a7eef0a..8862c626ea6e4c 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_to_rules_table/use_add_to_rules_table.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_to_rules_table/use_add_to_rules_table.test.tsx @@ -6,9 +6,8 @@ */ import React from 'react'; -import { fireEvent, render as rTLRender } from '@testing-library/react'; -import { waitFor } from '@testing-library/dom'; -import { act, renderHook } from '@testing-library/react-hooks'; +import { fireEvent, render as rTLRender, waitFor, act } from '@testing-library/react'; +import { renderHook } from '@testing-library/react-hooks'; import type { EuiTableFieldDataColumnType } from '@elastic/eui'; import type { Rule } from '../../../../rule_management/logic/types'; import { getRulesSchemaMock } from '../../../../../../common/api/detection_engine/model/rule_schema/mocks'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/item_comments/index.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/item_comments/index.test.tsx index 47933db0b35223..6dd8684eaeaac4 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/item_comments/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/item_comments/index.test.tsx @@ -6,6 +6,7 @@ */ import React from 'react'; +import { act, fireEvent, render } from '@testing-library/react'; import { mountWithIntl } from '@kbn/test-jest-helpers'; import { EuiTextArea } from '@elastic/eui'; @@ -13,6 +14,7 @@ import { ExceptionItemComments } from '.'; import { TestProviders } from '../../../../common/mock'; import { useCurrentUser } from '../../../../common/lib/kibana'; import { shallow } from 'enzyme'; +import { MAX_COMMENT_LENGTH } from '../../../../../common/constants'; jest.mock('../../../../common/lib/kibana'); @@ -38,6 +40,7 @@ describe('ExceptionItemComments', () => { ); @@ -65,6 +68,7 @@ describe('ExceptionItemComments', () => { ); @@ -92,6 +96,7 @@ describe('ExceptionItemComments', () => { ); @@ -106,6 +111,7 @@ describe('ExceptionItemComments', () => { ); @@ -122,6 +128,7 @@ describe('ExceptionItemComments', () => { ); @@ -152,10 +159,53 @@ describe('ExceptionItemComments', () => { ]} newCommentValue={''} newCommentOnChange={mockOnCommentChange} + setCommentError={jest.fn()} /> ); expect(wrapper.find('[data-test-subj="exceptionItemCommentsAccordion"]').exists()).toBeTruthy(); }); + + it('it calls setCommentError on comment error update change', async () => { + const mockSetCommentError = jest.fn(); + const { getByLabelText, queryByText } = render( + + + + ); + + const commentInput = getByLabelText('Comment Input'); + + const commentErrorMessage = `The length of the comment is too long. The maximum length is ${MAX_COMMENT_LENGTH} characters.`; + expect(queryByText(commentErrorMessage)).toBeNull(); + + // Put comment with the length above maximum allowed + act(() => { + fireEvent.change(commentInput, { + target: { + value: [...new Array(MAX_COMMENT_LENGTH + 1).keys()].map((_) => 'a').join(''), + }, + }); + fireEvent.blur(commentInput); + }); + expect(queryByText(commentErrorMessage)).not.toBeNull(); + expect(mockSetCommentError).toHaveBeenCalledWith(true); + + // Put comment with the allowed length + act(() => { + fireEvent.change(commentInput, { + target: { + value: 'Updating my new comment', + }, + }); + fireEvent.blur(commentInput); + }); + expect(queryByText(commentErrorMessage)).toBeNull(); + expect(mockSetCommentError).toHaveBeenCalledWith(false); + }); }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/item_comments/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/item_comments/index.tsx index 0f32e2b4d1ab8a..f262c7a07754a2 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/item_comments/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/item_comments/index.tsx @@ -5,19 +5,21 @@ * 2.0. */ -import React, { memo, useState, useCallback, useMemo } from 'react'; +import React, { memo, useState, useCallback, useMemo, useEffect } from 'react'; import styled, { css } from 'styled-components'; import type { EuiCommentProps } from '@elastic/eui'; import { EuiTextArea, EuiFlexGroup, EuiFlexItem, + EuiFormRow, EuiAvatar, EuiAccordion, EuiCommentList, EuiText, } from '@elastic/eui'; import type { Comment } from '@kbn/securitysolution-io-ts-list-types'; +import { MAX_COMMENT_LENGTH } from '../../../../../common/constants'; import * as i18n from './translations'; import { useCurrentUser } from '../../../../common/lib/kibana'; import { getFormattedComments } from '../../utils/helpers'; @@ -28,6 +30,7 @@ interface ExceptionItemCommentsProps { accordionTitle?: JSX.Element; initialIsOpen?: boolean; newCommentOnChange: (value: string) => void; + setCommentError: (errorExists: boolean) => void; } const COMMENT_ACCORDION_BUTTON_CLASS_NAME = 'exceptionCommentAccordionButton'; @@ -53,8 +56,11 @@ export const ExceptionItemComments = memo(function ExceptionItemComments({ accordionTitle, initialIsOpen = false, newCommentOnChange, + setCommentError, }: ExceptionItemCommentsProps) { + const [errorExists, setErrorExists] = useState(false); const [shouldShowComments, setShouldShowComments] = useState(false); + const currentUser = useCurrentUser(); const fullName = currentUser?.fullName; const userName = currentUser?.username; @@ -73,9 +79,14 @@ export const ExceptionItemComments = memo(function ExceptionItemComments({ return userName && userName.length > 0 ? userName : i18n.UNKNOWN_AVATAR_NAME; }, [fullName, userEmail, userName]); + useEffect(() => { + setCommentError(errorExists); + }, [errorExists, setCommentError]); + const handleOnChange = useCallback( (event: React.ChangeEvent) => { newCommentOnChange(event.target.value); + setErrorExists(event.target.value.length > MAX_COMMENT_LENGTH); }, [newCommentOnChange] ); @@ -121,14 +132,20 @@ export const ExceptionItemComments = memo(function ExceptionItemComments({ - + + + diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/item_comments/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/item_comments/translations.ts index afe20c6aada982..90c3d9bd0bc48a 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/item_comments/translations.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/item_comments/translations.ts @@ -32,3 +32,10 @@ export const COMMENTS_HIDE = (comments: number) => values: { comments }, defaultMessage: 'Hide ({comments}) {comments, plural, =1 {Comment} other {Comments}}', }); + +export const COMMENT_MAX_LENGTH_ERROR = (length: number) => + i18n.translate('xpack.securitySolution.rule_exceptions.itemComments.maxLengthError', { + values: { length }, + defaultMessage: + 'The length of the comment is too long. The maximum length is {length} characters.', + }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.test.ts index 9e09a1754a04dc..226d0a2bd16edd 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.test.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.test.ts @@ -16,9 +16,9 @@ import { getRulesSchemaMock, } from '../../../../common/api/detection_engine/model/rule_schema/mocks'; import { - BulkActionType, - BulkActionEditType, -} from '../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; + BulkActionTypeEnum, + BulkActionEditTypeEnum, +} from '../../../../common/api/detection_engine/rule_management'; import { rulesMock } from '../logic/mock'; import type { FindRulesReferencedByExceptionsListProp } from '../logic/types'; @@ -701,7 +701,9 @@ describe('Detections Rules API', () => { }); test('passes a query', async () => { - await performBulkAction({ bulkAction: { type: BulkActionType.enable, query: 'some query' } }); + await performBulkAction({ + bulkAction: { type: BulkActionTypeEnum.enable, query: 'some query' }, + }); expect(fetchMock).toHaveBeenCalledWith( '/api/detection_engine/rules/_bulk_action', @@ -720,7 +722,7 @@ describe('Detections Rules API', () => { test('passes ids', async () => { await performBulkAction({ - bulkAction: { type: BulkActionType.disable, ids: ['ruleId1', 'ruleId2'] }, + bulkAction: { type: BulkActionTypeEnum.disable, ids: ['ruleId1', 'ruleId2'] }, }); expect(fetchMock).toHaveBeenCalledWith( @@ -741,10 +743,10 @@ describe('Detections Rules API', () => { test('passes edit payload', async () => { await performBulkAction({ bulkAction: { - type: BulkActionType.edit, + type: BulkActionTypeEnum.edit, ids: ['ruleId1'], editPayload: [ - { type: BulkActionEditType.add_index_patterns, value: ['some-index-pattern'] }, + { type: BulkActionEditTypeEnum.add_index_patterns, value: ['some-index-pattern'] }, ], }, }); @@ -767,7 +769,7 @@ describe('Detections Rules API', () => { test('executes dry run', async () => { await performBulkAction({ - bulkAction: { type: BulkActionType.disable, query: 'some query' }, + bulkAction: { type: BulkActionTypeEnum.disable, query: 'some query' }, dryRun: true, }); @@ -787,7 +789,7 @@ describe('Detections Rules API', () => { test('returns result', async () => { const result = await performBulkAction({ bulkAction: { - type: BulkActionType.disable, + type: BulkActionTypeEnum.disable, query: 'some query', }, }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.ts index 860e0fc86e8501..70f0a56ef74cd6 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.ts @@ -27,12 +27,16 @@ import type { ReviewRuleInstallationResponseBody, } from '../../../../common/api/detection_engine/prebuilt_rules'; import type { + BulkDuplicateRules, + BulkActionEditPayload, + BulkActionType, CoverageOverviewResponse, GetRuleManagementFiltersResponse, } from '../../../../common/api/detection_engine/rule_management'; import { RULE_MANAGEMENT_FILTERS_URL, RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL, + BulkActionTypeEnum, } from '../../../../common/api/detection_engine/rule_management'; import type { BulkActionsDryRunErrCode } from '../../../../common/constants'; import { @@ -54,11 +58,6 @@ import { import type { RulesReferencedByExceptionListsSchema } from '../../../../common/api/detection_engine/rule_exceptions'; import { DETECTION_ENGINE_RULES_EXCEPTIONS_REFERENCE_URL } from '../../../../common/api/detection_engine/rule_exceptions'; -import type { - BulkActionDuplicatePayload, - BulkActionEditPayload, -} from '../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; -import { BulkActionType } from '../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; import type { PreviewResponse, RuleResponse } from '../../../../common/api/detection_engine'; import { KibanaServices } from '../../../common/lib/kibana'; @@ -331,18 +330,18 @@ export type QueryOrIds = { query: string; ids?: undefined } | { query?: undefine type PlainBulkAction = { type: Exclude< BulkActionType, - BulkActionType.edit | BulkActionType.export | BulkActionType.duplicate + BulkActionTypeEnum['edit'] | BulkActionTypeEnum['export'] | BulkActionTypeEnum['duplicate'] >; } & QueryOrIds; type EditBulkAction = { - type: BulkActionType.edit; + type: BulkActionTypeEnum['edit']; editPayload: BulkActionEditPayload[]; } & QueryOrIds; type DuplicateBulkAction = { - type: BulkActionType.duplicate; - duplicatePayload?: BulkActionDuplicatePayload; + type: BulkActionTypeEnum['duplicate']; + duplicatePayload?: BulkDuplicateRules['duplicate']; } & QueryOrIds; export type BulkAction = PlainBulkAction | EditBulkAction | DuplicateBulkAction; @@ -368,9 +367,9 @@ export async function performBulkAction({ action: bulkAction.type, query: bulkAction.query, ids: bulkAction.ids, - edit: bulkAction.type === BulkActionType.edit ? bulkAction.editPayload : undefined, + edit: bulkAction.type === BulkActionTypeEnum.edit ? bulkAction.editPayload : undefined, duplicate: - bulkAction.type === BulkActionType.duplicate ? bulkAction.duplicatePayload : undefined, + bulkAction.type === BulkActionTypeEnum.duplicate ? bulkAction.duplicatePayload : undefined, }; return KibanaServices.get().http.fetch(DETECTION_ENGINE_RULES_BULK_ACTION, { @@ -392,7 +391,7 @@ export type BulkExportResponse = Blob; */ export async function bulkExportRules(queryOrIds: QueryOrIds): Promise { const params = { - action: BulkActionType.export, + action: BulkActionTypeEnum.export, query: queryOrIds.query, ids: queryOrIds.ids, }; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_bulk_action_mutation.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_bulk_action_mutation.ts index 1a52bbb0a8194f..9e54e41f1b0911 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_bulk_action_mutation.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_bulk_action_mutation.ts @@ -7,7 +7,7 @@ import type { UseMutationOptions } from '@tanstack/react-query'; import { useMutation } from '@tanstack/react-query'; import type { IHttpFetchError } from '@kbn/core/public'; -import { BulkActionType } from '../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import { BulkActionTypeEnum } from '../../../../../common/api/detection_engine/rule_management'; import type { BulkActionErrorResponse, BulkActionResponse, PerformBulkActionProps } from '../api'; import { performBulkAction } from '../api'; import { DETECTION_ENGINE_RULES_BULK_ACTION } from '../../../../../common/constants'; @@ -59,8 +59,8 @@ export const useBulkActionMutation = ( response?.attributes?.results?.updated ?? error?.body?.attributes?.results?.updated; switch (actionType) { - case BulkActionType.enable: - case BulkActionType.disable: { + case BulkActionTypeEnum.enable: + case BulkActionTypeEnum.disable: { invalidateFetchRuleByIdQuery(); invalidateFetchCoverageOverviewQuery(); if (updatedRules) { @@ -72,7 +72,7 @@ export const useBulkActionMutation = ( } break; } - case BulkActionType.delete: + case BulkActionTypeEnum.delete: invalidateFindRulesQuery(); invalidateFetchRuleByIdQuery(); invalidateFetchRuleManagementFilters(); @@ -81,12 +81,12 @@ export const useBulkActionMutation = ( invalidateFetchPrebuiltRulesUpgradeReviewQuery(); invalidateFetchCoverageOverviewQuery(); break; - case BulkActionType.duplicate: + case BulkActionTypeEnum.duplicate: invalidateFindRulesQuery(); invalidateFetchRuleManagementFilters(); invalidateFetchCoverageOverviewQuery(); break; - case BulkActionType.edit: + case BulkActionTypeEnum.edit: if (updatedRules) { // We have a list of updated rules, no need to invalidate all updateRulesCache(updatedRules); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/translations.ts index 40877d8fcb2fd5..99bad79536bd7e 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/translations.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/translations.ts @@ -6,54 +6,57 @@ */ import type { HTTPError } from '../../../../../common/detection_engine/types'; -import type { BulkActionEditPayload } from '../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; -import { - BulkActionEditType, +import type { + BulkActionEditPayload, BulkActionType, -} from '../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +} from '../../../../../common/api/detection_engine/rule_management'; +import { + BulkActionEditTypeEnum, + BulkActionTypeEnum, +} from '../../../../../common/api/detection_engine/rule_management'; import * as i18n from '../../../../detections/pages/detection_engine/rules/translations'; import type { BulkActionResponse, BulkActionSummary } from '../../api/api'; export function summarizeBulkSuccess(action: BulkActionType): string { switch (action) { - case BulkActionType.export: + case BulkActionTypeEnum.export: return i18n.RULES_BULK_EXPORT_SUCCESS; - case BulkActionType.duplicate: + case BulkActionTypeEnum.duplicate: return i18n.RULES_BULK_DUPLICATE_SUCCESS; - case BulkActionType.delete: + case BulkActionTypeEnum.delete: return i18n.RULES_BULK_DELETE_SUCCESS; - case BulkActionType.enable: + case BulkActionTypeEnum.enable: return i18n.RULES_BULK_ENABLE_SUCCESS; - case BulkActionType.disable: + case BulkActionTypeEnum.disable: return i18n.RULES_BULK_DISABLE_SUCCESS; - case BulkActionType.edit: + case BulkActionTypeEnum.edit: return i18n.RULES_BULK_EDIT_SUCCESS; } } export function explainBulkSuccess( - action: Exclude, + action: Exclude, summary: BulkActionSummary ): string { switch (action) { - case BulkActionType.export: + case BulkActionTypeEnum.export: return getExportSuccessToastMessage(summary.succeeded, summary.total); - case BulkActionType.duplicate: + case BulkActionTypeEnum.duplicate: return i18n.RULES_BULK_DUPLICATE_SUCCESS_DESCRIPTION(summary.succeeded); - case BulkActionType.delete: + case BulkActionTypeEnum.delete: return i18n.RULES_BULK_DELETE_SUCCESS_DESCRIPTION(summary.succeeded); - case BulkActionType.enable: + case BulkActionTypeEnum.enable: return i18n.RULES_BULK_ENABLE_SUCCESS_DESCRIPTION(summary.succeeded); - case BulkActionType.disable: + case BulkActionTypeEnum.disable: return i18n.RULES_BULK_DISABLE_SUCCESS_DESCRIPTION(summary.succeeded); } } @@ -67,9 +70,9 @@ export function explainBulkEditSuccess( if ( editPayload.some( (x) => - x.type === BulkActionEditType.add_index_patterns || - x.type === BulkActionEditType.set_index_patterns || - x.type === BulkActionEditType.delete_index_patterns + x.type === BulkActionEditTypeEnum.add_index_patterns || + x.type === BulkActionEditTypeEnum.set_index_patterns || + x.type === BulkActionEditTypeEnum.delete_index_patterns ) ) { return `${i18n.RULES_BULK_EDIT_SUCCESS_DESCRIPTION( @@ -83,22 +86,22 @@ export function explainBulkEditSuccess( export function summarizeBulkError(action: BulkActionType): string { switch (action) { - case BulkActionType.export: + case BulkActionTypeEnum.export: return i18n.RULES_BULK_EXPORT_FAILURE; - case BulkActionType.duplicate: + case BulkActionTypeEnum.duplicate: return i18n.RULES_BULK_DUPLICATE_FAILURE; - case BulkActionType.delete: + case BulkActionTypeEnum.delete: return i18n.RULES_BULK_DELETE_FAILURE; - case BulkActionType.enable: + case BulkActionTypeEnum.enable: return i18n.RULES_BULK_ENABLE_FAILURE; - case BulkActionType.disable: + case BulkActionTypeEnum.disable: return i18n.RULES_BULK_DISABLE_FAILURE; - case BulkActionType.edit: + case BulkActionTypeEnum.edit: return i18n.RULES_BULK_EDIT_FAILURE; } } @@ -112,22 +115,22 @@ export function explainBulkError(action: BulkActionType, error: HTTPError): stri } switch (action) { - case BulkActionType.export: + case BulkActionTypeEnum.export: return i18n.RULES_BULK_EXPORT_FAILURE_DESCRIPTION(summary.failed); - case BulkActionType.duplicate: + case BulkActionTypeEnum.duplicate: return i18n.RULES_BULK_DUPLICATE_FAILURE_DESCRIPTION(summary.failed); - case BulkActionType.delete: + case BulkActionTypeEnum.delete: return i18n.RULES_BULK_DELETE_FAILURE_DESCRIPTION(summary.failed); - case BulkActionType.enable: + case BulkActionTypeEnum.enable: return i18n.RULES_BULK_ENABLE_FAILURE_DESCRIPTION(summary.failed); - case BulkActionType.disable: + case BulkActionTypeEnum.disable: return i18n.RULES_BULK_DISABLE_FAILURE_DESCRIPTION(summary.failed); - case BulkActionType.edit: + case BulkActionTypeEnum.edit: return i18n.RULES_BULK_EDIT_FAILURE_DESCRIPTION(summary.failed, summary.skipped); } } diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_bulk_export.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_bulk_export.test.ts index 1ac62109cc6263..968b40c7a6026a 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_bulk_export.test.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_bulk_export.test.ts @@ -6,7 +6,7 @@ */ import { renderHook } from '@testing-library/react-hooks'; -import { BulkActionType } from '../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import { BulkActionTypeEnum } from '../../../../../common/api/detection_engine/rule_management'; import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { useRulesTableContextOptional } from '../../../rule_management_ui/components/rules_table/rules_table/rules_table_context'; import { useBulkExportMutation } from '../../api/hooks/use_bulk_export_mutation'; @@ -92,7 +92,7 @@ describe('useBulkExport', () => { expect(setLoadingRules).toHaveBeenCalledWith({ ids: ['ruleId1', 'ruleId2'], - action: BulkActionType.export, + action: BulkActionTypeEnum.export, }); }); @@ -101,7 +101,7 @@ describe('useBulkExport', () => { expect(setLoadingRules).toHaveBeenCalledWith({ ids: [], - action: BulkActionType.export, + action: BulkActionTypeEnum.export, }); }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_bulk_export.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_bulk_export.ts index 5554ba22963024..651b2b0e4b86ce 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_bulk_export.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_bulk_export.ts @@ -6,7 +6,7 @@ */ import { useCallback } from 'react'; -import { BulkActionType } from '../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import { BulkActionTypeEnum } from '../../../../../common/api/detection_engine/rule_management'; import { useRulesTableContextOptional } from '../../../rule_management_ui/components/rules_table/rules_table/rules_table_context'; import { useBulkExportMutation } from '../../api/hooks/use_bulk_export_mutation'; import { useShowBulkErrorToast } from './use_show_bulk_error_toast'; @@ -24,12 +24,12 @@ export function useBulkExport() { async (queryOrIds: QueryOrIds) => { try { setLoadingRules?.({ - ids: queryOrIds.ids ?? guessRuleIdsForBulkAction(BulkActionType.export), - action: BulkActionType.export, + ids: queryOrIds.ids ?? guessRuleIdsForBulkAction(BulkActionTypeEnum.export), + action: BulkActionTypeEnum.export, }); return await mutateAsync(queryOrIds); } catch (error) { - showBulkErrorToast({ actionType: BulkActionType.export, error }); + showBulkErrorToast({ actionType: BulkActionTypeEnum.export, error }); } finally { setLoadingRules?.({ ids: [], action: null }); } diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_download_exported_rules.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_download_exported_rules.ts index 94c26b278d1b11..7be106090bcfad 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_download_exported_rules.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_download_exported_rules.ts @@ -6,7 +6,7 @@ */ import { useCallback } from 'react'; -import { BulkActionType } from '../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import { BulkActionTypeEnum } from '../../../../../common/api/detection_engine/rule_management'; import { downloadBlob } from '../../../../common/utils/download_blob'; import * as i18n from '../../../../detections/pages/detection_engine/rules/translations'; import { getExportedRulesCounts } from '../../../rule_management_ui/components/rules_table/helpers'; @@ -27,11 +27,11 @@ export function useDownloadExportedRules() { try { downloadBlob(response, DEFAULT_EXPORT_FILENAME); showBulkSuccessToast({ - actionType: BulkActionType.export, + actionType: BulkActionTypeEnum.export, summary: await getExportedRulesCounts(response), }); } catch (error) { - showBulkErrorToast({ actionType: BulkActionType.export, error }); + showBulkErrorToast({ actionType: BulkActionTypeEnum.export, error }); } }, [showBulkSuccessToast, showBulkErrorToast] diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_execute_bulk_action.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_execute_bulk_action.test.ts index 3c211247b94ec9..6309d8b629bc25 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_execute_bulk_action.test.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_execute_bulk_action.test.ts @@ -6,7 +6,7 @@ */ import { renderHook } from '@testing-library/react-hooks'; -import { BulkActionType } from '../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import { BulkActionTypeEnum } from '../../../../../common/api/detection_engine/rule_management'; import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { METRIC_TYPE, TELEMETRY_EVENT, track } from '../../../../common/lib/telemetry'; import { useBulkActionMutation } from '../../api/hooks/use_bulk_action_mutation'; @@ -61,7 +61,7 @@ describe('useExecuteBulkAction', () => { it('executes bulk action', async () => { const bulkAction = { - type: BulkActionType.enable, + type: BulkActionTypeEnum.enable, query: 'some query', } as const; @@ -73,7 +73,7 @@ describe('useExecuteBulkAction', () => { describe('state handlers', () => { it('shows success toast upon completion', async () => { await executeBulkAction({ - type: BulkActionType.enable, + type: BulkActionTypeEnum.enable, ids: ['ruleId1'], }); @@ -84,7 +84,7 @@ describe('useExecuteBulkAction', () => { it('does not shows success toast upon completion if suppressed', async () => { await executeBulkAction( { - type: BulkActionType.enable, + type: BulkActionTypeEnum.enable, ids: ['ruleId1'], }, { suppressSuccessToast: true } @@ -100,7 +100,7 @@ describe('useExecuteBulkAction', () => { }); await executeBulkAction({ - type: BulkActionType.enable, + type: BulkActionTypeEnum.enable, ids: ['ruleId1'], }); @@ -126,31 +126,31 @@ describe('useExecuteBulkAction', () => { it('sets the loading state before execution', async () => { await executeBulkAction({ - type: BulkActionType.enable, + type: BulkActionTypeEnum.enable, ids: ['ruleId1', 'ruleId2'], }); expect(setLoadingRules).toHaveBeenCalledWith({ ids: ['ruleId1', 'ruleId2'], - action: BulkActionType.enable, + action: BulkActionTypeEnum.enable, }); }); it('sets the empty loading state before execution when query is set', async () => { await executeBulkAction({ - type: BulkActionType.enable, + type: BulkActionTypeEnum.enable, query: 'some query', }); expect(setLoadingRules).toHaveBeenCalledWith({ ids: [], - action: BulkActionType.enable, + action: BulkActionTypeEnum.enable, }); }); it('clears loading state for the processing rules after execution', async () => { await executeBulkAction({ - type: BulkActionType.enable, + type: BulkActionTypeEnum.enable, ids: ['ruleId1', 'ruleId2'], }); @@ -163,7 +163,7 @@ describe('useExecuteBulkAction', () => { }); await executeBulkAction({ - type: BulkActionType.enable, + type: BulkActionTypeEnum.enable, ids: ['ruleId1', 'ruleId2'], }); @@ -174,7 +174,7 @@ describe('useExecuteBulkAction', () => { describe('telemetry', () => { it('sends for enable action', async () => { await executeBulkAction({ - type: BulkActionType.enable, + type: BulkActionTypeEnum.enable, query: 'some query', }); @@ -184,7 +184,7 @@ describe('useExecuteBulkAction', () => { it('sends for disable action', async () => { await executeBulkAction({ - type: BulkActionType.disable, + type: BulkActionTypeEnum.disable, query: 'some query', }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_execute_bulk_action.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_execute_bulk_action.ts index 9fbfb0c310f20c..0a294647aad3f4 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_execute_bulk_action.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_execute_bulk_action.ts @@ -9,7 +9,8 @@ import type { NavigateToAppOptions } from '@kbn/core/public'; import { useCallback } from 'react'; import type { BulkActionResponse } from '..'; import { APP_UI_ID } from '../../../../../common/constants'; -import { BulkActionType } from '../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import type { BulkActionType } from '../../../../../common/api/detection_engine/rule_management'; +import { BulkActionTypeEnum } from '../../../../../common/api/detection_engine/rule_management'; import { SecurityPageName } from '../../../../app/types'; import { getEditRuleUrl } from '../../../../common/components/link_to/redirect_to_detection_engine'; import { METRIC_TYPE, TELEMETRY_EVENT, track } from '../../../../common/lib/telemetry'; @@ -58,7 +59,7 @@ export const useExecuteBulkAction = (options?: UseExecuteBulkActionOptions) => { actionType: bulkAction.type, summary: response.attributes.summary, editPayload: - bulkAction.type === BulkActionType.edit ? bulkAction.editPayload : undefined, + bulkAction.type === BulkActionTypeEnum.edit ? bulkAction.editPayload : undefined, }); } @@ -83,14 +84,14 @@ export const useExecuteBulkAction = (options?: UseExecuteBulkActionOptions) => { }; function sendTelemetry(action: BulkActionType, response: BulkActionResponse): void { - if (action !== BulkActionType.disable && action !== BulkActionType.enable) { + if (action !== BulkActionTypeEnum.disable && action !== BulkActionTypeEnum.enable) { return; } if (response.attributes.results.updated.some((rule) => rule.immutable)) { track( METRIC_TYPE.COUNT, - action === BulkActionType.enable + action === BulkActionTypeEnum.enable ? TELEMETRY_EVENT.SIEM_RULE_ENABLED : TELEMETRY_EVENT.SIEM_RULE_DISABLED ); @@ -99,7 +100,7 @@ function sendTelemetry(action: BulkActionType, response: BulkActionResponse): vo if (response.attributes.results.updated.some((rule) => !rule.immutable)) { track( METRIC_TYPE.COUNT, - action === BulkActionType.disable + action === BulkActionTypeEnum.disable ? TELEMETRY_EVENT.CUSTOM_RULE_DISABLED : TELEMETRY_EVENT.CUSTOM_RULE_ENABLED ); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_guess_rule_ids_for_bulk_action.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_guess_rule_ids_for_bulk_action.ts index ce262ce940f431..2a1acc7a3d4c82 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_guess_rule_ids_for_bulk_action.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_guess_rule_ids_for_bulk_action.ts @@ -6,7 +6,8 @@ */ import { useCallback } from 'react'; -import { BulkActionType } from '../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import type { BulkActionType } from '../../../../../common/api/detection_engine/rule_management'; +import { BulkActionTypeEnum } from '../../../../../common/api/detection_engine/rule_management'; import { useRulesTableContextOptional } from '../../../rule_management_ui/components/rules_table/rules_table/rules_table_context'; export function useGuessRuleIdsForBulkAction(): (bulkActionType: BulkActionType) => string[] { @@ -16,9 +17,9 @@ export function useGuessRuleIdsForBulkAction(): (bulkActionType: BulkActionType) (bulkActionType: BulkActionType) => { const allRules = rulesTableContext?.state.isAllSelected ? rulesTableContext.state.rules : []; const processingRules = - bulkActionType === BulkActionType.enable + bulkActionType === BulkActionTypeEnum.enable ? allRules.filter((x) => !x.enabled) - : bulkActionType === BulkActionType.disable + : bulkActionType === BulkActionTypeEnum.disable ? allRules.filter((x) => x.enabled) : allRules; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_show_bulk_error_toast.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_show_bulk_error_toast.ts index 3f9230a36da341..bb72429ad6b0b4 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_show_bulk_error_toast.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_show_bulk_error_toast.ts @@ -8,7 +8,7 @@ import { useCallback } from 'react'; import type { HTTPError } from '../../../../../common/detection_engine/types'; import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; -import type { BulkActionType } from '../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import type { BulkActionType } from '../../../../../common/api/detection_engine/rule_management'; import { explainBulkError, summarizeBulkError } from './translations'; interface ShowBulkErrorToastProps { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_show_bulk_success_toast.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_show_bulk_success_toast.ts index dfc2ca5dcb918b..03113c772818cf 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_show_bulk_success_toast.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_show_bulk_success_toast.ts @@ -8,8 +8,11 @@ import { useCallback } from 'react'; import type { BulkActionSummary } from '..'; import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; -import type { BulkActionEditPayload } from '../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; -import { BulkActionType } from '../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import type { + BulkActionEditPayload, + BulkActionType, +} from '../../../../../common/api/detection_engine/rule_management'; +import { BulkActionTypeEnum } from '../../../../../common/api/detection_engine/rule_management'; import { explainBulkEditSuccess, explainBulkSuccess, summarizeBulkSuccess } from './translations'; interface ShowBulkSuccessToastProps { @@ -24,7 +27,7 @@ export function useShowBulkSuccessToast() { return useCallback( ({ actionType, summary, editPayload }: ShowBulkSuccessToastProps) => { const text = - actionType === BulkActionType.edit + actionType === BulkActionTypeEnum.edit ? explainBulkEditSuccess(editPayload ?? [], summary) : explainBulkSuccess(actionType, summary); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts index d44c4effd265f8..94a3d47c90ecf1 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts @@ -5,12 +5,11 @@ * 2.0. */ -import * as t from 'io-ts'; +import * as z from 'zod'; import type { RuleSnooze } from '@kbn/alerting-plugin/common'; import type { Type } from '@kbn/securitysolution-io-ts-alerting-types'; import type { NamespaceType } from '@kbn/securitysolution-io-ts-list-types'; -import { PositiveInteger } from '@kbn/securitysolution-io-ts-types'; import type { RuleSnoozeSettings } from '@kbn/triggers-actions-ui-plugin/public/types'; import type { WarningSchema } from '../../../../common/api/detection_engine'; import type { RuleExecutionStatus } from '../../../../common/api/detection_engine/rule_monitoring'; @@ -49,11 +48,11 @@ export interface PatchRuleProps { export type Rule = RuleResponse; -export type PaginationOptions = t.TypeOf; -export const PaginationOptions = t.type({ - page: PositiveInteger, - perPage: PositiveInteger, - total: PositiveInteger, +export type PaginationOptions = z.infer; +export const PaginationOptions = z.object({ + page: z.number().int().min(0), + perPage: z.number().int().min(0), + total: z.number().int().min(0), }); export interface FetchRulesProps { @@ -81,8 +80,8 @@ export interface RulesSnoozeSettingsBatchResponse { data: RuleSnoozeSettingsResponse[]; } -export type SortingOptions = t.TypeOf; -export const SortingOptions = t.type({ +export type SortingOptions = z.infer; +export const SortingOptions = z.object({ field: FindRulesSortField, order: SortOrder, }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/bulk_action_dry_run_confirmation.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/bulk_action_dry_run_confirmation.tsx index 9e3e9c8b602b19..23bb9106d62cfc 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/bulk_action_dry_run_confirmation.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/bulk_action_dry_run_confirmation.tsx @@ -10,7 +10,7 @@ import { EuiConfirmModal } from '@elastic/eui'; import * as i18n from '../../../../../detections/pages/detection_engine/rules/translations'; import { BulkActionRuleErrorsList } from './bulk_action_rule_errors_list'; -import { BulkActionType } from '../../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import { BulkActionTypeEnum } from '../../../../../../common/api/detection_engine/rule_management'; import { assertUnreachable } from '../../../../../../common/utility_types'; import type { BulkActionForConfirmation, DryRunResult } from './types'; @@ -20,9 +20,9 @@ const getActionRejectedTitle = ( failedRulesCount: number ) => { switch (bulkAction) { - case BulkActionType.edit: + case BulkActionTypeEnum.edit: return i18n.BULK_EDIT_CONFIRMATION_REJECTED_TITLE(failedRulesCount); - case BulkActionType.export: + case BulkActionTypeEnum.export: return i18n.BULK_EXPORT_CONFIRMATION_REJECTED_TITLE(failedRulesCount); default: assertUnreachable(bulkAction); @@ -34,9 +34,9 @@ const getActionConfirmLabel = ( succeededRulesCount: number ) => { switch (bulkAction) { - case BulkActionType.edit: + case BulkActionTypeEnum.edit: return i18n.BULK_EDIT_CONFIRMATION_CONFIRM(succeededRulesCount); - case BulkActionType.export: + case BulkActionTypeEnum.export: return i18n.BULK_EXPORT_CONFIRMATION_CONFIRM(succeededRulesCount); default: assertUnreachable(bulkAction); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/bulk_action_rule_errors_list.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/bulk_action_rule_errors_list.test.tsx index 05a27a17274a1a..5b90a457a6bd44 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/bulk_action_rule_errors_list.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/bulk_action_rule_errors_list.test.tsx @@ -13,7 +13,7 @@ import { render, screen } from '@testing-library/react'; import { BulkActionRuleErrorsList } from './bulk_action_rule_errors_list'; import { BulkActionsDryRunErrCode } from '../../../../../../common/constants'; import type { DryRunResult } from './types'; -import { BulkActionType } from '../../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import { BulkActionTypeEnum } from '../../../../../../common/api/detection_engine/rule_management'; const Wrapper: FC = ({ children }) => { return ( @@ -26,7 +26,7 @@ const Wrapper: FC = ({ children }) => { describe('Component BulkEditRuleErrorsList', () => { test('should not render component if no errors present', () => { const { container } = render( - , + , { wrapper: Wrapper, } @@ -46,9 +46,12 @@ describe('Component BulkEditRuleErrorsList', () => { ruleIds: ['rule:1'], }, ]; - render(, { - wrapper: Wrapper, - }); + render( + , + { + wrapper: Wrapper, + } + ); expect(screen.getByText("2 rules can't be edited (test failure)")).toBeInTheDocument(); expect(screen.getByText("1 rule can't be edited (another failure)")).toBeInTheDocument(); @@ -80,9 +83,12 @@ describe('Component BulkEditRuleErrorsList', () => { ruleIds: ['rule:1', 'rule:2'], }, ]; - render(, { - wrapper: Wrapper, - }); + render( + , + { + wrapper: Wrapper, + } + ); expect(screen.getByText(value)).toBeInTheDocument(); }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/bulk_action_rule_errors_list.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/bulk_action_rule_errors_list.tsx index 674206446f85ca..907e67f658bc45 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/bulk_action_rule_errors_list.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/bulk_action_rule_errors_list.tsx @@ -10,7 +10,7 @@ import { EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { BulkActionsDryRunErrCode } from '../../../../../../common/constants'; -import { BulkActionType } from '../../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import { BulkActionTypeEnum } from '../../../../../../common/api/detection_engine/rule_management'; import type { DryRunResult, BulkActionForConfirmation } from './types'; @@ -132,7 +132,7 @@ const BulkActionRuleErrorsListComponent = ({ {ruleErrors.map(({ message, errorCode, ruleIds }) => { const rulesCount = ruleIds.length; switch (bulkAction) { - case BulkActionType.edit: + case BulkActionTypeEnum.edit: return ( ); - case BulkActionType.export: + case BulkActionTypeEnum.export: return ( { switch (editAction) { - case BulkActionEditType.add_index_patterns: - case BulkActionEditType.delete_index_patterns: - case BulkActionEditType.set_index_patterns: + case BulkActionEditTypeEnum.add_index_patterns: + case BulkActionEditTypeEnum.delete_index_patterns: + case BulkActionEditTypeEnum.set_index_patterns: return ; - case BulkActionEditType.add_tags: - case BulkActionEditType.delete_tags: - case BulkActionEditType.set_tags: + case BulkActionEditTypeEnum.add_tags: + case BulkActionEditTypeEnum.delete_tags: + case BulkActionEditTypeEnum.set_tags: return ; - case BulkActionEditType.set_timeline: + case BulkActionEditTypeEnum.set_timeline: return ; - case BulkActionEditType.add_rule_actions: - case BulkActionEditType.set_rule_actions: + case BulkActionEditTypeEnum.add_rule_actions: + case BulkActionEditTypeEnum.set_rule_actions: return ; - case BulkActionEditType.set_schedule: + case BulkActionEditTypeEnum.set_schedule: return ; default: diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/forms/index_patterns_form.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/forms/index_patterns_form.tsx index c9d4900e9adc74..e124e23bd0aea5 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/forms/index_patterns_form.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/forms/index_patterns_form.tsx @@ -14,8 +14,8 @@ import * as i18n from '../../../../../../detections/pages/detection_engine/rules import { DEFAULT_INDEX_KEY } from '../../../../../../../common/constants'; import { useKibana } from '../../../../../../common/lib/kibana'; -import { BulkActionEditType } from '../../../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; -import type { BulkActionEditPayload } from '../../../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import { BulkActionEditTypeEnum } from '../../../../../../../common/api/detection_engine/rule_management'; +import type { BulkActionEditPayload } from '../../../../../../../common/api/detection_engine/rule_management'; import type { FormSchema } from '../../../../../../shared_imports'; import { @@ -31,9 +31,9 @@ import { BulkEditFormWrapper } from './bulk_edit_form_wrapper'; const CommonUseField = getUseField({ component: Field }); type IndexPatternsEditActions = - | BulkActionEditType.add_index_patterns - | BulkActionEditType.delete_index_patterns - | BulkActionEditType.set_index_patterns; + | BulkActionEditTypeEnum['add_index_patterns'] + | BulkActionEditTypeEnum['delete_index_patterns'] + | BulkActionEditTypeEnum['set_index_patterns']; interface IndexPatternsFormData { index: string[]; @@ -70,7 +70,7 @@ const initialFormData: IndexPatternsFormData = { }; const getFormConfig = (editAction: IndexPatternsEditActions) => - editAction === BulkActionEditType.add_index_patterns + editAction === BulkActionEditTypeEnum.add_index_patterns ? { indexLabel: i18n.BULK_EDIT_FLYOUT_FORM_ADD_INDEX_PATTERNS_LABEL, indexHelpText: i18n.BULK_EDIT_FLYOUT_FORM_ADD_INDEX_PATTERNS_HELP_TEXT, @@ -115,13 +115,11 @@ const IndexPatternsFormComponent = ({ return; } - const payload = { + onConfirm({ value: data.index, - type: data.overwrite ? BulkActionEditType.set_index_patterns : editAction, + type: data.overwrite ? BulkActionEditTypeEnum.set_index_patterns : editAction, overwrite_data_views: data.overwriteDataViews, - }; - - onConfirm(payload); + }); }; return ( @@ -140,7 +138,7 @@ const IndexPatternsFormComponent = ({ }, }} /> - {editAction === BulkActionEditType.add_index_patterns && ( + {editAction === BulkActionEditTypeEnum.add_index_patterns && ( )} - {editAction === BulkActionEditType.add_index_patterns && ( + {editAction === BulkActionEditTypeEnum.add_index_patterns && ( )} - {editAction === BulkActionEditType.delete_index_patterns && ( + {editAction === BulkActionEditTypeEnum.delete_index_patterns && ( = { const initialFormData: TagsFormData = { tags: [], overwrite: false }; const getFormConfig = (editAction: TagsEditActions) => - editAction === BulkActionEditType.add_tags + editAction === BulkActionEditTypeEnum.add_tags ? { tagsLabel: i18n.BULK_EDIT_FLYOUT_FORM_ADD_TAGS_LABEL, tagsHelpText: i18n.BULK_EDIT_FLYOUT_FORM_ADD_TAGS_HELP_TEXT, @@ -97,12 +97,10 @@ const TagsFormComponent = ({ editAction, rulesCount, onClose, onConfirm }: TagsF return; } - const payload = { + onConfirm({ value: data.tags, - type: data.overwrite ? BulkActionEditType.set_tags : editAction, - }; - - onConfirm(payload); + type: data.overwrite ? BulkActionEditTypeEnum.set_tags : editAction, + }); }; return ( @@ -121,7 +119,7 @@ const TagsFormComponent = ({ editAction, rulesCount, onClose, onConfirm }: TagsF }, }} /> - {editAction === BulkActionEditType.add_tags ? ( + {editAction === BulkActionEditTypeEnum.add_tags ? ( { const timelineTitle = timelineId ? data.timeline.title : ''; onConfirm({ - type: BulkActionEditType.set_timeline, + type: BulkActionEditTypeEnum.set_timeline, value: { timeline_id: timelineId, timeline_title: timelineTitle, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/types.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/types.ts index 000a7e37a9cec1..409ee722c6383e 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/types.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/types.ts @@ -6,14 +6,14 @@ */ import type { BulkActionsDryRunErrCode } from '../../../../../../common/constants'; -import type { BulkActionType } from '../../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import type { BulkActionTypeEnum } from '../../../../../../common/api/detection_engine/rule_management'; /** * Only 2 bulk actions are supported for for confirmation dry run modal: * * export * * edit */ -export type BulkActionForConfirmation = BulkActionType.export | BulkActionType.edit; +export type BulkActionForConfirmation = BulkActionTypeEnum['export'] | BulkActionTypeEnum['edit']; /** * transformed results of dry run diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_actions.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_actions.tsx index a7c5e35ff3341e..41802a4738b8df 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_actions.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_actions.tsx @@ -14,11 +14,14 @@ import { euiThemeVars } from '@kbn/ui-theme'; import React, { useCallback } from 'react'; import { convertRulesFilterToKQL } from '../../../../../../common/detection_engine/rule_management/rule_filtering'; import { DuplicateOptions } from '../../../../../../common/detection_engine/rule_management/constants'; -import type { BulkActionEditPayload } from '../../../../../../common/api/detection_engine/rule_management'; -import { - BulkActionType, +import type { + BulkActionEditPayload, BulkActionEditType, } from '../../../../../../common/api/detection_engine/rule_management'; +import { + BulkActionTypeEnum, + BulkActionEditTypeEnum, +} from '../../../../../../common/api/detection_engine/rule_management'; import { isMlRule } from '../../../../../../common/machine_learning/helpers'; import { useAppToasts } from '../../../../../common/hooks/use_app_toasts'; import { BULK_RULE_ACTIONS } from '../../../../../common/lib/apm/user_actions'; @@ -106,7 +109,7 @@ export const useBulkActions = ({ : disabledRulesNoML.map(({ id }) => id); await executeBulkAction({ - type: BulkActionType.enable, + type: BulkActionTypeEnum.enable, ...(isAllSelected ? { query: kql } : { ids: ruleIds }), }); }; @@ -118,7 +121,7 @@ export const useBulkActions = ({ const enabledIds = selectedRules.filter(({ enabled }) => enabled).map(({ id }) => id); await executeBulkAction({ - type: BulkActionType.disable, + type: BulkActionTypeEnum.disable, ...(isAllSelected ? { query: kql } : { ids: enabledIds }), }); }; @@ -132,7 +135,7 @@ export const useBulkActions = ({ return; } await executeBulkAction({ - type: BulkActionType.duplicate, + type: BulkActionTypeEnum.duplicate, duplicatePayload: { include_exceptions: modalDuplicationConfirmationResult === DuplicateOptions.withExceptions || @@ -159,7 +162,7 @@ export const useBulkActions = ({ startTransaction({ name: BULK_RULE_ACTIONS.DELETE }); await executeBulkAction({ - type: BulkActionType.delete, + type: BulkActionTypeEnum.delete, ...(isAllSelected ? { query: kql } : { ids: selectedRuleIds }), }); }; @@ -183,7 +186,7 @@ export const useBulkActions = ({ // they can either cancel action or proceed with export of succeeded rules const hasActionBeenConfirmed = await showBulkActionConfirmation( transformExportDetailsToDryRunResult(details), - BulkActionType.export + BulkActionTypeEnum.export ); if (hasActionBeenConfirmed === false) { return; @@ -201,7 +204,7 @@ export const useBulkActions = ({ setIsPreflightInProgress(true); const dryRunResult = await executeBulkActionsDryRun({ - type: BulkActionType.edit, + type: BulkActionTypeEnum.edit, ...(isAllSelected ? { query: convertRulesFilterToKQL(filterOptions) } : { ids: selectedRuleIds }), @@ -213,7 +216,7 @@ export const useBulkActions = ({ // User has cancelled edit action or there are no custom rules to proceed const hasActionBeenConfirmed = await showBulkActionConfirmation( dryRunResult, - BulkActionType.edit + BulkActionTypeEnum.edit ); if (hasActionBeenConfirmed === false) { return; @@ -264,7 +267,7 @@ export const useBulkActions = ({ }, 5 * 1000); await executeBulkAction({ - type: BulkActionType.edit, + type: BulkActionTypeEnum.edit, ...prepareSearchParams({ ...(isAllSelected ? { filterOptions } : { selectedRuleIds }), dryRunResult, @@ -330,7 +333,7 @@ export const useBulkActions = ({ name: i18n.BULK_ACTION_ADD_RULE_ACTIONS, 'data-test-subj': 'addRuleActionsBulk', disabled: !hasActionsPrivileges || isEditDisabled, - onClick: handleBulkEdit(BulkActionEditType.add_rule_actions), + onClick: handleBulkEdit(BulkActionEditTypeEnum.add_rule_actions), toolTipContent: !hasActionsPrivileges ? i18n.LACK_OF_KIBANA_ACTIONS_FEATURE_PRIVILEGES : undefined, @@ -342,7 +345,7 @@ export const useBulkActions = ({ name: i18n.BULK_ACTION_SET_SCHEDULE, 'data-test-subj': 'setScheduleBulk', disabled: isEditDisabled, - onClick: handleBulkEdit(BulkActionEditType.set_schedule), + onClick: handleBulkEdit(BulkActionEditTypeEnum.set_schedule), toolTipContent: missingActionPrivileges ? i18n.LACK_OF_KIBANA_ACTIONS_FEATURE_PRIVILEGES : undefined, @@ -354,7 +357,7 @@ export const useBulkActions = ({ name: i18n.BULK_ACTION_APPLY_TIMELINE_TEMPLATE, 'data-test-subj': 'applyTimelineTemplateBulk', disabled: isEditDisabled, - onClick: handleBulkEdit(BulkActionEditType.set_timeline), + onClick: handleBulkEdit(BulkActionEditTypeEnum.set_timeline), toolTipContent: missingActionPrivileges ? i18n.LACK_OF_KIBANA_ACTIONS_FEATURE_PRIVILEGES : undefined, @@ -407,7 +410,7 @@ export const useBulkActions = ({ key: i18n.BULK_ACTION_ADD_TAGS, name: i18n.BULK_ACTION_ADD_TAGS, 'data-test-subj': 'addTagsBulkEditRule', - onClick: handleBulkEdit(BulkActionEditType.add_tags), + onClick: handleBulkEdit(BulkActionEditTypeEnum.add_tags), disabled: isEditDisabled, toolTipContent: missingActionPrivileges ? i18n.LACK_OF_KIBANA_ACTIONS_FEATURE_PRIVILEGES @@ -418,7 +421,7 @@ export const useBulkActions = ({ key: i18n.BULK_ACTION_DELETE_TAGS, name: i18n.BULK_ACTION_DELETE_TAGS, 'data-test-subj': 'deleteTagsBulkEditRule', - onClick: handleBulkEdit(BulkActionEditType.delete_tags), + onClick: handleBulkEdit(BulkActionEditTypeEnum.delete_tags), disabled: isEditDisabled, toolTipContent: missingActionPrivileges ? i18n.LACK_OF_KIBANA_ACTIONS_FEATURE_PRIVILEGES @@ -435,7 +438,7 @@ export const useBulkActions = ({ key: i18n.BULK_ACTION_ADD_INDEX_PATTERNS, name: i18n.BULK_ACTION_ADD_INDEX_PATTERNS, 'data-test-subj': 'addIndexPatternsBulkEditRule', - onClick: handleBulkEdit(BulkActionEditType.add_index_patterns), + onClick: handleBulkEdit(BulkActionEditTypeEnum.add_index_patterns), disabled: isEditDisabled, toolTipContent: missingActionPrivileges ? i18n.LACK_OF_KIBANA_ACTIONS_FEATURE_PRIVILEGES @@ -446,7 +449,7 @@ export const useBulkActions = ({ key: i18n.BULK_ACTION_DELETE_INDEX_PATTERNS, name: i18n.BULK_ACTION_DELETE_INDEX_PATTERNS, 'data-test-subj': 'deleteIndexPatternsBulkEditRule', - onClick: handleBulkEdit(BulkActionEditType.delete_index_patterns), + onClick: handleBulkEdit(BulkActionEditTypeEnum.delete_index_patterns), disabled: isEditDisabled, toolTipContent: missingActionPrivileges ? i18n.LACK_OF_KIBANA_ACTIONS_FEATURE_PRIVILEGES diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_actions_confirmation.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_actions_confirmation.ts index 9ce813fc6d9a2a..5ef159bed856b1 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_actions_confirmation.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_actions_confirmation.ts @@ -11,11 +11,23 @@ import { useBoolState } from '../../../../../common/hooks/use_bool_state'; import type { DryRunResult, BulkActionForConfirmation } from './types'; +interface BulkActionsConfirmation { + bulkActionsDryRunResult: DryRunResult | undefined; + bulkAction: BulkActionForConfirmation | undefined; + isBulkActionConfirmationVisible: boolean; + showBulkActionConfirmation: ( + result: DryRunResult | undefined, + action: BulkActionForConfirmation + ) => Promise; + cancelBulkActionConfirmation: () => void; + approveBulkActionConfirmation: () => void; +} + /** * hook that controls bulk actions confirmation modal window and its content */ // TODO Why does this hook exist? Consider removing it altogether -export const useBulkActionsConfirmation = () => { +export const useBulkActionsConfirmation = (): BulkActionsConfirmation => { const [bulkAction, setBulkAction] = useState(); const [dryRunResult, setDryRunResult] = useState(); const [isBulkActionConfirmationVisible, showModal, hideModal] = useBoolState(); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_edit_form_flyout.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_edit_form_flyout.ts index 260e187e46fbef..f2dc15233cb6b8 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_edit_form_flyout.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_edit_form_flyout.ts @@ -10,10 +10,20 @@ import { useAsyncConfirmation } from '../rules_table/use_async_confirmation'; import type { BulkActionEditPayload, BulkActionEditType, -} from '../../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +} from '../../../../../../common/api/detection_engine/rule_management'; import { useBoolState } from '../../../../../common/hooks/use_bool_state'; -export const useBulkEditFormFlyout = () => { +interface UseBulkEditFormFlyout { + bulkEditActionType: BulkActionEditType | undefined; + isBulkEditFlyoutVisible: boolean; + handleBulkEditFormConfirm: (data: BulkActionEditPayload) => void; + handleBulkEditFormCancel: () => void; + completeBulkEditForm: ( + editActionType: BulkActionEditType + ) => Promise; +} + +export const useBulkEditFormFlyout = (): UseBulkEditFormFlyout => { const dataFormRef = useRef(null); const [actionType, setActionType] = useState(); const [isBulkEditFlyoutVisible, showBulkEditFlyout, hideBulkEditFlyout] = useBoolState(); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/compute_dry_run_edit_payload.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/compute_dry_run_edit_payload.test.ts index 3adae50d99adf9..0549306036fd2b 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/compute_dry_run_edit_payload.test.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/compute_dry_run_edit_payload.test.ts @@ -5,19 +5,20 @@ * 2.0. */ -import { BulkActionEditType } from '../../../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import type { BulkActionEditType } from '../../../../../../../common/api/detection_engine/rule_management'; +import { BulkActionEditTypeEnum } from '../../../../../../../common/api/detection_engine/rule_management'; import { computeDryRunEditPayload } from './compute_dry_run_edit_payload'; describe('computeDryRunEditPayload', () => { - test.each([ - [BulkActionEditType.set_index_patterns, []], - [BulkActionEditType.delete_index_patterns, []], - [BulkActionEditType.add_index_patterns, []], - [BulkActionEditType.add_tags, []], - [BulkActionEditType.delete_index_patterns, []], - [BulkActionEditType.set_tags, []], - [BulkActionEditType.set_timeline, { timeline_id: '', timeline_title: '' }], + test.each<[BulkActionEditType, unknown]>([ + [BulkActionEditTypeEnum.set_index_patterns, []], + [BulkActionEditTypeEnum.delete_index_patterns, []], + [BulkActionEditTypeEnum.add_index_patterns, []], + [BulkActionEditTypeEnum.add_tags, []], + [BulkActionEditTypeEnum.delete_index_patterns, []], + [BulkActionEditTypeEnum.set_tags, []], + [BulkActionEditTypeEnum.set_timeline, { timeline_id: '', timeline_title: '' }], ])('should return correct payload for bulk edit action %s', (editAction, value) => { const payload = computeDryRunEditPayload(editAction); expect(payload).toHaveLength(1); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/compute_dry_run_edit_payload.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/compute_dry_run_edit_payload.ts index d31bbfaa917903..ba5d565e393d09 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/compute_dry_run_edit_payload.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/compute_dry_run_edit_payload.ts @@ -5,8 +5,11 @@ * 2.0. */ -import type { BulkActionEditPayload } from '../../../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; -import { BulkActionEditType } from '../../../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import type { + BulkActionEditPayload, + BulkActionEditType, +} from '../../../../../../../common/api/detection_engine/rule_management'; +import { BulkActionEditTypeEnum } from '../../../../../../../common/api/detection_engine/rule_management'; import { assertUnreachable } from '../../../../../../../common/utility_types'; /** @@ -17,9 +20,9 @@ import { assertUnreachable } from '../../../../../../../common/utility_types'; */ export function computeDryRunEditPayload(editAction: BulkActionEditType): BulkActionEditPayload[] { switch (editAction) { - case BulkActionEditType.add_index_patterns: - case BulkActionEditType.delete_index_patterns: - case BulkActionEditType.set_index_patterns: + case BulkActionEditTypeEnum.add_index_patterns: + case BulkActionEditTypeEnum.delete_index_patterns: + case BulkActionEditTypeEnum.set_index_patterns: return [ { type: editAction, @@ -27,9 +30,9 @@ export function computeDryRunEditPayload(editAction: BulkActionEditType): BulkAc }, ]; - case BulkActionEditType.add_tags: - case BulkActionEditType.delete_tags: - case BulkActionEditType.set_tags: + case BulkActionEditTypeEnum.add_tags: + case BulkActionEditTypeEnum.delete_tags: + case BulkActionEditTypeEnum.set_tags: return [ { type: editAction, @@ -37,7 +40,7 @@ export function computeDryRunEditPayload(editAction: BulkActionEditType): BulkAc }, ]; - case BulkActionEditType.set_timeline: + case BulkActionEditTypeEnum.set_timeline: return [ { type: editAction, @@ -45,15 +48,15 @@ export function computeDryRunEditPayload(editAction: BulkActionEditType): BulkAc }, ]; - case BulkActionEditType.add_rule_actions: - case BulkActionEditType.set_rule_actions: + case BulkActionEditTypeEnum.add_rule_actions: + case BulkActionEditTypeEnum.set_rule_actions: return [ { type: editAction, value: { actions: [] }, }, ]; - case BulkActionEditType.set_schedule: + case BulkActionEditTypeEnum.set_schedule: return [ { type: editAction, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/guided_onboarding/rules_management_tour.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/guided_onboarding/rules_management_tour.tsx index e27910df0b7e03..fbb81fd0b66f42 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/guided_onboarding/rules_management_tour.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/guided_onboarding/rules_management_tour.tsx @@ -12,7 +12,7 @@ import React, { useCallback, useEffect, useMemo } from 'react'; import useObservable from 'react-use/lib/useObservable'; import { of } from 'rxjs'; import { siemGuideId } from '../../../../../../../common/guided_onboarding/siem_guide_config'; -import { BulkActionType } from '../../../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import { BulkActionTypeEnum } from '../../../../../../../common/api/detection_engine/rule_management'; import { useKibana } from '../../../../../../common/lib/kibana'; import { useFindRulesQuery } from '../../../../../rule_management/api/hooks/use_find_rules_query'; import { useExecuteBulkAction } from '../../../../../rule_management/logic/bulk_actions/use_execute_bulk_action'; @@ -113,7 +113,7 @@ export const RulesManagementTour = () => { const enableDemoRule = useCallback(async () => { if (demoRule) { await executeBulkAction({ - type: BulkActionType.enable, + type: BulkActionTypeEnum.enable, ids: [demoRule.id], }); } diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/rules_table_saved_state.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/rules_table_saved_state.ts index 84c23a248a0db6..1a4efe7517ac93 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/rules_table_saved_state.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/rules_table_saved_state.ts @@ -5,41 +5,45 @@ * 2.0. */ -import * as t from 'io-ts'; -import { enumeration } from '@kbn/securitysolution-io-ts-types'; -import { SortingOptions, PaginationOptions } from '../../../../rule_management/logic'; -import { TRuleExecutionStatus } from '../../../../../../common/api/detection_engine/rule_monitoring/model/execution_status'; +import * as z from 'zod'; +import { RuleExecutionStatus } from '../../../../../../common/api/detection_engine'; +import { PaginationOptions, SortingOptions } from '../../../../rule_management/logic'; export enum RuleSource { Prebuilt = 'prebuilt', Custom = 'custom', } -export type RulesTableSavedFilter = t.TypeOf; -export const RulesTableSavedFilter = t.partial({ - searchTerm: t.string, - source: enumeration('RuleSource', RuleSource), - tags: t.array(t.string), - enabled: t.boolean, - ruleExecutionStatus: TRuleExecutionStatus, -}); - -export type RulesTableSavedSorting = t.TypeOf; -export const RulesTableSavedSorting = t.partial({ - field: SortingOptions.props.field, - order: SortingOptions.props.order, -}); - -export type RulesTableStorageSavedPagination = t.TypeOf; -export const RulesTableStorageSavedPagination = t.partial({ - perPage: PaginationOptions.props.perPage, -}); - -export type RulesTableUrlSavedPagination = t.TypeOf; -export const RulesTableUrlSavedPagination = t.partial({ - page: PaginationOptions.props.page, - perPage: PaginationOptions.props.perPage, -}); +export const RulesTableSavedFilter = z + .object({ + searchTerm: z.string(), + source: z.nativeEnum(RuleSource), + tags: z.array(z.string()), + enabled: z.boolean(), + ruleExecutionStatus: RuleExecutionStatus, + }) + .partial(); + +export type RulesTableSavedFilter = z.infer; + +export const RulesTableSavedSorting = SortingOptions.pick({ + field: true, + order: true, +}).partial(); + +export type RulesTableSavedSorting = z.infer; + +export const RulesTableStorageSavedPagination = PaginationOptions.pick({ + perPage: true, +}).partial(); + +export type RulesTableStorageSavedPagination = z.infer; + +export type RulesTableUrlSavedPagination = z.infer; +export const RulesTableUrlSavedPagination = PaginationOptions.pick({ + page: true, + perPage: true, +}).partial(); export type RulesTableStorageSavedState = RulesTableSavedFilter & RulesTableSavedSorting & diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/use_rules_table_saved_state.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/use_rules_table_saved_state.ts index bc1b28ee72a418..3055c9cbdcbba1 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/use_rules_table_saved_state.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/use_rules_table_saved_state.ts @@ -6,7 +6,7 @@ */ import type { Storage } from '@kbn/kibana-utils-plugin/public'; -import { validateNonExact } from '@kbn/securitysolution-io-ts-utils'; +import { safeParseResult } from '@kbn/zod-helpers'; import { useGetInitialUrlParamValue } from '../../../../../common/utils/global_query_string/helpers'; import { RULES_TABLE_MAX_PAGE_SIZE } from '../../../../../../common/constants'; import { useKibana } from '../../../../../common/lib/kibana'; @@ -57,8 +57,8 @@ function validateState( urlState: RulesTableUrlSavedState | null, storageState: RulesTableStorageSavedState | null ): [RulesTableSavedFilter, RulesTableSavedSorting, RulesTableUrlSavedPagination] { - const [filterFromUrl] = validateNonExact(urlState, RulesTableSavedFilter); - const [filterFromStorage] = validateNonExact(storageState, RulesTableSavedFilter); + const filterFromUrl = safeParseResult(urlState, RulesTableSavedFilter); + const filterFromStorage = safeParseResult(storageState, RulesTableSavedFilter); // We have to expose filter, sorting and pagination objects by explicitly specifying each field // since urlState and/or storageState may contain unnecessary fields (e.g. outdated or explicitly added by user) // and validateNonExact doesn't truncate fields not included in the type RulesTableSavedFilter and etc. @@ -71,15 +71,15 @@ function validateState( filterFromUrl?.ruleExecutionStatus ?? filterFromStorage?.ruleExecutionStatus, }; - const [sortingFromUrl] = validateNonExact(urlState, RulesTableSavedSorting); - const [sortingFromStorage] = validateNonExact(storageState, RulesTableSavedSorting); + const sortingFromUrl = safeParseResult(urlState, RulesTableSavedSorting); + const sortingFromStorage = safeParseResult(storageState, RulesTableSavedSorting); const sorting = { field: sortingFromUrl?.field ?? sortingFromStorage?.field, order: sortingFromUrl?.order ?? sortingFromStorage?.order, - }; + } as const; - const [paginationFromUrl] = validateNonExact(urlState, RulesTableUrlSavedPagination); - const [paginationFromStorage] = validateNonExact(storageState, RulesTableStorageSavedPagination); + const paginationFromUrl = safeParseResult(urlState, RulesTableUrlSavedPagination); + const paginationFromStorage = safeParseResult(storageState, RulesTableStorageSavedPagination); const pagination = { page: paginationFromUrl?.page, // We don't persist page number in the session storage since it may be outdated when restored perPage: paginationFromUrl?.perPage ?? paginationFromStorage?.perPage, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_columns.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_columns.tsx index 3525793caa3a39..acf43ebf2c36ee 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_columns.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_columns.tsx @@ -16,10 +16,7 @@ import { SecurityPageName, SHOW_RELATED_INTEGRATIONS_SETTING, } from '../../../../../common/constants'; -import type { - DurationMetric, - RuleExecutionSummary, -} from '../../../../../common/api/detection_engine/rule_monitoring'; +import type { RuleExecutionSummary } from '../../../../../common/api/detection_engine/rule_monitoring'; import { isMlRule } from '../../../../../common/machine_learning/helpers'; import { getEmptyTagValue } from '../../../../common/components/empty_value'; import { RuleSnoozeBadge } from '../../../rule_management/components/rule_snooze_badge'; @@ -402,7 +399,7 @@ export const useMonitoringColumns = ({ tooltipContent={i18n.COLUMN_INDEXING_TIMES_TOOLTIP} /> ), - render: (value: DurationMetric | undefined) => ( + render: (value: number | undefined) => ( {value != null ? value.toFixed() : getEmptyTagValue()} @@ -419,7 +416,7 @@ export const useMonitoringColumns = ({ tooltipContent={i18n.COLUMN_QUERY_TIMES_TOOLTIP} /> ), - render: (value: DurationMetric | undefined) => ( + render: (value: number | undefined) => ( {value != null ? value.toFixed() : getEmptyTagValue()} @@ -459,7 +456,7 @@ export const useMonitoringColumns = ({ } /> ), - render: (value: DurationMetric | undefined) => ( + render: (value: number | undefined) => ( {value != null ? moment.duration(value, 'seconds').humanize() : getEmptyTagValue()} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_rules_table_actions.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_rules_table_actions.tsx index 1d9d6ad45c8fa8..04fc59da5e027e 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_rules_table_actions.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_rules_table_actions.tsx @@ -9,7 +9,7 @@ import type { DefaultItemAction } from '@elastic/eui'; import { EuiToolTip } from '@elastic/eui'; import React from 'react'; import { DuplicateOptions } from '../../../../../common/detection_engine/rule_management/constants'; -import { BulkActionType } from '../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import { BulkActionTypeEnum } from '../../../../../common/api/detection_engine/rule_management'; import { SINGLE_RULE_ACTIONS } from '../../../../common/lib/apm/user_actions'; import { useStartTransaction } from '../../../../common/lib/apm/use_start_transaction'; import { useKibana } from '../../../../common/lib/kibana'; @@ -75,7 +75,7 @@ export const useRulesTableActions = ({ return; } const result = await executeBulkAction({ - type: BulkActionType.duplicate, + type: BulkActionTypeEnum.duplicate, ids: [rule.id], duplicatePayload: { include_exceptions: @@ -123,7 +123,7 @@ export const useRulesTableActions = ({ startTransaction({ name: SINGLE_RULE_ACTIONS.DELETE }); await executeBulkAction({ - type: BulkActionType.delete, + type: BulkActionTypeEnum.delete, ids: [rule.id], }); }, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/coverage_overview_dashboard_context.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/coverage_overview_dashboard_context.tsx index 3a2424664f8ab1..057a75d9a5a51c 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/coverage_overview_dashboard_context.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/coverage_overview/coverage_overview_dashboard_context.tsx @@ -15,7 +15,7 @@ import React, { } from 'react'; import { invariant } from '../../../../../common/utils/invariant'; import { - BulkActionType, + BulkActionTypeEnum, CoverageOverviewRuleActivity, CoverageOverviewRuleSource, } from '../../../../../common/api/detection_engine'; @@ -114,7 +114,7 @@ export const CoverageOverviewDashboardContextProvider = ({ const enableAllDisabled = useCallback( async (ruleIds: string[]) => { - await executeBulkAction({ type: BulkActionType.enable, ids: ruleIds }); + await executeBulkAction({ type: BulkActionTypeEnum.enable, ids: ruleIds }); }, [executeBulkAction] ); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/api/__mocks__/api_client.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/api/__mocks__/api_client.ts index b5b8f201f0ff3c..a70a9bd66671bb 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/api/__mocks__/api_client.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/api/__mocks__/api_client.ts @@ -10,8 +10,8 @@ import type { GetRuleExecutionResultsResponse, } from '../../../../../common/api/detection_engine/rule_monitoring'; import { - LogLevel, - RuleExecutionEventType, + LogLevelEnum, + RuleExecutionEventTypeEnum, } from '../../../../../common/api/detection_engine/rule_monitoring'; import type { @@ -30,8 +30,8 @@ export const api: jest.Mocked = { { timestamp: '2021-12-29T10:42:59.996Z', sequence: 0, - level: LogLevel.info, - type: RuleExecutionEventType['status-change'], + level: LogLevelEnum.info, + type: RuleExecutionEventTypeEnum['status-change'], execution_id: 'execution-id-1', message: 'Rule changed status to "succeeded". Rule execution completed without errors', }, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/api/api_client.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/api/api_client.test.ts index d1317e2f742520..640cc1a86e4232 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/api/api_client.test.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/api/api_client.test.ts @@ -12,11 +12,12 @@ import type { GetRuleExecutionResultsResponse, } from '../../../../common/api/detection_engine/rule_monitoring'; import { - LogLevel, - RuleExecutionEventType, + LogLevelEnum, + RuleExecutionEventTypeEnum, } from '../../../../common/api/detection_engine/rule_monitoring'; import { api } from './api_client'; +import type { FetchRuleExecutionEventsArgs } from './api_client_interface'; jest.mock('../../../common/lib/kibana'); @@ -74,7 +75,7 @@ describe('Rule Monitoring API Client', () => { const ISO_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/; - it.each([ + it.each<[string, Omit, Record]>([ [ 'search term filter', { searchTerm: 'something to search' }, @@ -82,12 +83,12 @@ describe('Rule Monitoring API Client', () => { ], [ 'event types filter', - { eventTypes: [RuleExecutionEventType.message] }, + { eventTypes: [RuleExecutionEventTypeEnum.message] }, { event_types: 'message' }, ], [ 'log level filter', - { logLevels: [LogLevel.warn, LogLevel.error] }, + { logLevels: [LogLevelEnum.warn, LogLevelEnum.error] }, { log_levels: 'warn,error' }, ], [ diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/basic/filters/event_type_filter/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/basic/filters/event_type_filter/index.tsx index 2c87a184f5b1cf..5edc079ef5c427 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/basic/filters/event_type_filter/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/basic/filters/event_type_filter/index.tsx @@ -7,8 +7,7 @@ import React, { useCallback } from 'react'; -import type { RuleExecutionEventType } from '../../../../../../../common/api/detection_engine/rule_monitoring'; -import { RULE_EXECUTION_EVENT_TYPES } from '../../../../../../../common/api/detection_engine/rule_monitoring'; +import { RuleExecutionEventType } from '../../../../../../../common/api/detection_engine/rule_monitoring'; import { EventTypeIndicator } from '../../indicators/event_type_indicator'; import { MultiselectFilter } from '../multiselect_filter'; @@ -28,7 +27,7 @@ const EventTypeFilterComponent: React.FC = ({ selectedItem dataTestSubj="eventTypeFilter" title={i18n.FILTER_TITLE} - items={RULE_EXECUTION_EVENT_TYPES} + items={RuleExecutionEventType.options} selectedItems={selectedItems} onSelectionChange={onChange} renderItem={renderItem} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/basic/indicators/event_type_indicator/utils.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/basic/indicators/event_type_indicator/utils.ts index 07b3b3a6b096ae..9e862152280788 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/basic/indicators/event_type_indicator/utils.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/basic/indicators/event_type_indicator/utils.ts @@ -6,18 +6,19 @@ */ import type { IconType } from '@elastic/eui'; -import { RuleExecutionEventType } from '../../../../../../../common/api/detection_engine/rule_monitoring'; +import type { RuleExecutionEventType } from '../../../../../../../common/api/detection_engine/rule_monitoring'; +import { RuleExecutionEventTypeEnum } from '../../../../../../../common/api/detection_engine/rule_monitoring'; import { assertUnreachable } from '../../../../../../../common/utility_types'; import * as i18n from './translations'; export const getBadgeIcon = (type: RuleExecutionEventType): IconType => { switch (type) { - case RuleExecutionEventType.message: + case RuleExecutionEventTypeEnum.message: return 'console'; - case RuleExecutionEventType['status-change']: + case RuleExecutionEventTypeEnum['status-change']: return 'dot'; - case RuleExecutionEventType['execution-metrics']: + case RuleExecutionEventTypeEnum['execution-metrics']: return 'gear'; default: return assertUnreachable(type, 'Unknown rule execution event type'); @@ -26,11 +27,11 @@ export const getBadgeIcon = (type: RuleExecutionEventType): IconType => { export const getBadgeText = (type: RuleExecutionEventType): string => { switch (type) { - case RuleExecutionEventType.message: + case RuleExecutionEventTypeEnum.message: return i18n.TYPE_MESSAGE; - case RuleExecutionEventType['status-change']: + case RuleExecutionEventTypeEnum['status-change']: return i18n.TYPE_STATUS_CHANGE; - case RuleExecutionEventType['execution-metrics']: + case RuleExecutionEventTypeEnum['execution-metrics']: return i18n.TYPE_EXECUTION_METRICS; default: return assertUnreachable(type, 'Unknown rule execution event type'); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/basic/indicators/log_level_indicator/utils.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/basic/indicators/log_level_indicator/utils.ts index 639c648de02417..702d3edddda5b7 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/basic/indicators/log_level_indicator/utils.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/basic/indicators/log_level_indicator/utils.ts @@ -7,20 +7,21 @@ import { upperCase } from 'lodash'; import type { IconColor } from '@elastic/eui'; -import { LogLevel } from '../../../../../../../common/api/detection_engine/rule_monitoring'; +import type { LogLevel } from '../../../../../../../common/api/detection_engine/rule_monitoring'; +import { LogLevelEnum } from '../../../../../../../common/api/detection_engine/rule_monitoring'; import { assertUnreachable } from '../../../../../../../common/utility_types'; export const getBadgeColor = (logLevel: LogLevel): IconColor => { switch (logLevel) { - case LogLevel.trace: + case LogLevelEnum.trace: return 'hollow'; - case LogLevel.debug: + case LogLevelEnum.debug: return 'hollow'; - case LogLevel.info: + case LogLevelEnum.info: return 'default'; - case LogLevel.warn: + case LogLevelEnum.warn: return 'warning'; - case LogLevel.error: + case LogLevelEnum.error: return 'danger'; default: return assertUnreachable(logLevel, 'Unknown log level'); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/basic/tables/use_sorting.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/basic/tables/use_sorting.ts index 39e48c3997478c..5fb9c0fb32215e 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/basic/tables/use_sorting.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/basic/tables/use_sorting.ts @@ -11,11 +11,18 @@ import type { SortOrder } from '../../../../../../common/api/detection_engine'; type TableItem = Record; +interface SortingState { + sort: { + field: keyof T; + direction: SortOrder; + }; +} + export const useSorting = (defaultField: keyof T, defaultOrder: SortOrder) => { const [sortField, setSortField] = useState(defaultField); const [sortOrder, setSortOrder] = useState(defaultOrder); - const state = useMemo(() => { + const state = useMemo>(() => { return { sort: { field: sortField, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_events_table/use_execution_events.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_events_table/use_execution_events.test.tsx index 866e0e44b6c778..5459968b6c497e 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_events_table/use_execution_events.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_events_table/use_execution_events.test.tsx @@ -10,8 +10,8 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { renderHook, cleanup } from '@testing-library/react-hooks'; import { - LogLevel, - RuleExecutionEventType, + LogLevelEnum, + RuleExecutionEventTypeEnum, } from '../../../../../common/api/detection_engine/rule_monitoring'; import { useExecutionEvents } from './use_execution_events'; @@ -85,8 +85,8 @@ describe('useExecutionEvents', () => { { timestamp: '2021-12-29T10:42:59.996Z', sequence: 0, - level: LogLevel.info, - type: RuleExecutionEventType['status-change'], + level: LogLevelEnum.info, + type: RuleExecutionEventTypeEnum['status-change'], execution_id: 'execution-id-1', message: 'Rule changed status to "succeeded". Rule execution completed without errors', }, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/constants.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/constants.ts index d99be0c13e70be..19d6111b5a9363 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/constants.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/constants.ts @@ -5,13 +5,13 @@ * 2.0. */ -import { RESPONSE_ACTION_TYPES } from '../../../common/api/detection_engine/model/rule_response_actions'; +import { ResponseActionTypesEnum } from '../../../common/api/detection_engine/model/rule_response_actions'; export const getActionDetails = (actionTypeId: string) => { switch (actionTypeId) { - case RESPONSE_ACTION_TYPES.OSQUERY: + case ResponseActionTypesEnum['.osquery']: return { logo: 'logoOsquery', name: 'Osquery' }; - case RESPONSE_ACTION_TYPES.ENDPOINT: + case ResponseActionTypesEnum['.endpoint']: return { logo: 'logoSecurity', name: 'Endpoint Security' }; // update when new responseActions are provided default: diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/get_supported_response_actions.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/get_supported_response_actions.ts index ad3e3f8392eb11..e8afdd91d1ff3a 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/get_supported_response_actions.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/get_supported_response_actions.ts @@ -6,10 +6,9 @@ */ import type { EnabledFeatures } from '@kbn/spaces-plugin/public/management/edit_space/enabled_features'; -import type { ResponseActionTypes } from '../../../common/api/detection_engine/model/rule_response_actions'; import { - RESPONSE_ACTION_TYPES, - SUPPORTED_RESPONSE_ACTION_TYPES, + ResponseActionTypes, + ResponseActionTypesEnum, } from '../../../common/api/detection_engine/model/rule_response_actions'; export interface ResponseActionType { @@ -29,9 +28,9 @@ export const getSupportedResponseActions = ( userPermissions: EnabledFeatures ): ResponseActionType[] => actionTypes.reduce((acc: ResponseActionType[], actionType) => { - const isEndpointAction = actionType.id === RESPONSE_ACTION_TYPES.ENDPOINT; + const isEndpointAction = actionType.id === ResponseActionTypesEnum['.endpoint']; if (!enabledFeatures.endpoint && isEndpointAction) return acc; - if (SUPPORTED_RESPONSE_ACTION_TYPES.includes(actionType.id)) + if (ResponseActionTypes.options.includes(actionType.id)) return [ ...acc, { ...actionType, disabled: isEndpointAction ? !userPermissions.endpoint : undefined }, @@ -39,14 +38,14 @@ export const getSupportedResponseActions = ( return acc; }, []); -export const responseActionTypes = [ +export const responseActionTypes: ResponseActionType[] = [ { - id: RESPONSE_ACTION_TYPES.OSQUERY, + id: ResponseActionTypesEnum['.osquery'], name: 'Osquery', iconClass: 'logoOsquery', }, { - id: RESPONSE_ACTION_TYPES.ENDPOINT, + id: ResponseActionTypesEnum['.endpoint'], name: 'Endpoint Security', iconClass: 'logoSecurity', }, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/response_action_type_form.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/response_action_type_form.tsx index 7b176b96c2948e..97f3e932e81feb 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/response_action_type_form.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/response_action_type_form.tsx @@ -21,7 +21,7 @@ import styled from 'styled-components'; import { useCheckEndpointPermissions } from './endpoint/check_permissions'; import { EndpointResponseAction } from './endpoint/endpoint_response_action'; import type { RuleResponseAction } from '../../../common/api/detection_engine/model/rule_response_actions'; -import { RESPONSE_ACTION_TYPES } from '../../../common/api/detection_engine/model/rule_response_actions'; +import { ResponseActionTypesEnum } from '../../../common/api/detection_engine/model/rule_response_actions'; import { OsqueryResponseAction } from './osquery/osquery_response_action'; import { getActionDetails } from './constants'; import { useFormData } from '../../shared_imports'; @@ -48,10 +48,10 @@ const ResponseActionTypeFormComponent = ({ item, onDeleteAction }: ResponseActio const editDisabled = useCheckEndpointPermissions(action) ?? false; const getResponseActionTypeForm = useMemo(() => { - if (action?.actionTypeId === RESPONSE_ACTION_TYPES.OSQUERY) { + if (action?.actionTypeId === ResponseActionTypesEnum['.osquery']) { return ; } - if (action?.actionTypeId === RESPONSE_ACTION_TYPES.ENDPOINT) { + if (action?.actionTypeId === ResponseActionTypesEnum['.endpoint']) { return ; } // Place for other ResponseActionTypes diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/utils.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/utils.tsx index 5b1e57e6386f4c..22d190d80b9c41 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/utils.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/utils.tsx @@ -11,7 +11,7 @@ import { filter, reduce } from 'lodash'; import type { ECSMapping } from '@kbn/osquery-io-ts-types'; import type { RuleResponseAction } from '../../../common/api/detection_engine/model/rule_response_actions'; -import { RESPONSE_ACTION_TYPES } from '../../../common/api/detection_engine/model/rule_response_actions'; +import { ResponseActionTypesEnum } from '../../../common/api/detection_engine/model/rule_response_actions'; import { OsqueryParser } from '../../common/components/markdown_editor/plugins/osquery/parser'; interface OsqueryNoteQuery { @@ -38,7 +38,7 @@ export const getResponseActionsFromNote = ( (acc: { responseActions: RuleResponseAction[] }, { configuration }: OsqueryNoteQuery) => { const responseActionPath = 'responseActions'; acc[responseActionPath].push({ - actionTypeId: RESPONSE_ACTION_TYPES.OSQUERY, + actionTypeId: ResponseActionTypesEnum['.osquery'], params: { savedQueryId: undefined, packId: undefined, diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/mitre/subtechnique_fields.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/mitre/subtechnique_fields.tsx index efc542f9757a12..7645bc2c2c5790 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/mitre/subtechnique_fields.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/mitre/subtechnique_fields.tsx @@ -13,7 +13,6 @@ import { EuiFlexGroup, EuiFlexItem, } from '@elastic/eui'; -import { camelCase } from 'lodash/fp'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import styled from 'styled-components'; @@ -110,9 +109,9 @@ export const MitreAttackSubtechniqueFields: React.FC = ({ }, [field, onFieldChange, techniqueIndex, technique, threatIndex]); const updateSubtechnique = useCallback( - (index: number, value: string) => { + (index: number, optionId: string) => { const threats = [...(field.value as Threats)]; - const { id, reference, name } = subtechniquesOptions.find((t) => t.value === value) || { + const { id, reference, name } = subtechniquesOptions.find((t) => t.id === optionId) ?? { id: '', name: '', reference: '', @@ -170,7 +169,7 @@ export const MitreAttackSubtechniqueFields: React.FC = ({ : []), ...options.map((option) => ({ inputDisplay: <>{option.label}, - value: option.value, + value: option.id, disabled, })), ]} @@ -178,7 +177,7 @@ export const MitreAttackSubtechniqueFields: React.FC = ({ aria-label="" onChange={updateSubtechnique.bind(null, index)} fullWidth={true} - valueOfSelected={camelCase(subtechnique.name)} + valueOfSelected={subtechnique.id} data-test-subj="mitreAttackSubtechnique" disabled={disabled} placeholder={i18n.SUBTECHNIQUE_PLACEHOLDER} diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/mitre/technique_fields.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/mitre/technique_fields.tsx index 0215204a98cbdd..bb677801ecaa90 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/mitre/technique_fields.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/mitre/technique_fields.tsx @@ -13,7 +13,7 @@ import { EuiFlexGroup, EuiFlexItem, } from '@elastic/eui'; -import { kebabCase, camelCase } from 'lodash/fp'; +import { kebabCase } from 'lodash/fp'; import React, { useCallback, useEffect, useState } from 'react'; import styled, { css } from 'styled-components'; @@ -105,9 +105,9 @@ export const MitreAttackTechniqueFields: React.FC = ({ }, [field, threatIndex, onFieldChange]); const updateTechnique = useCallback( - (index: number, value: string) => { + (index: number, optionId: string) => { const threats = [...(field.value as Threats)]; - const { id, reference, name } = techniquesOptions.find((t) => t.value === value) || { + const { id, reference, name } = techniquesOptions.find((t) => t.id === optionId) ?? { id: '', name: '', reference: '', @@ -153,7 +153,7 @@ export const MitreAttackTechniqueFields: React.FC = ({ : []), ...options.map((option) => ({ inputDisplay: <>{option.label}, - value: option.value, + value: option.id, disabled, })), ]} @@ -161,7 +161,7 @@ export const MitreAttackTechniqueFields: React.FC = ({ aria-label="" onChange={updateTechnique.bind(null, index)} fullWidth={true} - valueOfSelected={camelCase(technique.name)} + valueOfSelected={technique.id} data-test-subj="mitreAttackTechnique" disabled={disabled} placeholder={i18n.TECHNIQUE_PLACEHOLDER} diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.tsx index 50c8a5bf50d1b0..9a351af0803c7e 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.tsx @@ -16,7 +16,7 @@ import React, { useCallback, useMemo } from 'react'; import styled from 'styled-components'; import { APP_UI_ID, SecurityPageName } from '../../../../../common/constants'; import { DuplicateOptions } from '../../../../../common/detection_engine/rule_management/constants'; -import { BulkActionType } from '../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import { BulkActionTypeEnum } from '../../../../../common/api/detection_engine/rule_management'; import { getRulesUrl } from '../../../../common/components/link_to/redirect_to_detection_engine'; import { useBoolState } from '../../../../common/hooks/use_bool_state'; import { SINGLE_RULE_ACTIONS } from '../../../../common/lib/apm/user_actions'; @@ -94,7 +94,7 @@ const RuleActionsOverflowComponent = ({ return; } const result = await executeBulkAction({ - type: BulkActionType.duplicate, + type: BulkActionTypeEnum.duplicate, ids: [rule.id], duplicatePayload: { include_exceptions: @@ -156,7 +156,7 @@ const RuleActionsOverflowComponent = ({ startTransaction({ name: SINGLE_RULE_ACTIONS.DELETE }); await executeBulkAction({ - type: BulkActionType.delete, + type: BulkActionTypeEnum.delete, ids: [rule.id], }); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_switch/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_switch/index.tsx index 7e4881800f738d..35434a711768e5 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_switch/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_switch/index.tsx @@ -9,7 +9,7 @@ import type { EuiSwitchEvent } from '@elastic/eui'; import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner, EuiSwitch } from '@elastic/eui'; import React, { useCallback, useMemo, useState } from 'react'; import styled from 'styled-components'; -import { BulkActionType } from '../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import { BulkActionTypeEnum } from '../../../../../common/api/detection_engine/rule_management'; import { SINGLE_RULE_ACTIONS } from '../../../../common/lib/apm/user_actions'; import { useStartTransaction } from '../../../../common/lib/apm/use_start_transaction'; import { useExecuteBulkAction } from '../../../../detection_engine/rule_management/logic/bulk_actions/use_execute_bulk_action'; @@ -60,7 +60,7 @@ export const RuleSwitchComponent = ({ await startMlJobsIfNeeded?.(); } const bulkActionResponse = await executeBulkAction({ - type: enableRule ? BulkActionType.enable : BulkActionType.disable, + type: enableRule ? BulkActionTypeEnum.enable : BulkActionTypeEnum.disable, ids: [id], }); if (bulkActionResponse?.attributes.results.updated.length) { diff --git a/x-pack/plugins/security_solution/public/entity_analytics/api/api.ts b/x-pack/plugins/security_solution/public/entity_analytics/api/api.ts index cc2b01f75da570..6b92583b1ddde6 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/api/api.ts +++ b/x-pack/plugins/security_solution/public/entity_analytics/api/api.ts @@ -20,7 +20,7 @@ import type { GetRiskEngineStatusResponse, InitRiskEngineResponse, DisableRiskEngineResponse, -} from '../../../server/lib/risk_engine/types'; +} from '../../../server/lib/entity_analytics/risk_engine/types'; import type { RiskScorePreviewRequestSchema } from '../../../common/risk_engine/risk_score_preview/request_schema'; /** diff --git a/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_disable_risk_engine_mutation.ts b/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_disable_risk_engine_mutation.ts index 997e93136339ed..68b63300061b61 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_disable_risk_engine_mutation.ts +++ b/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_disable_risk_engine_mutation.ts @@ -11,7 +11,7 @@ import { useInvalidateRiskEngineStatusQuery } from './use_risk_engine_status'; import type { EnableRiskEngineResponse, EnableDisableRiskEngineErrorResponse, -} from '../../../../server/lib/risk_engine/types'; +} from '../../../../server/lib/entity_analytics/risk_engine/types'; export const DISABLE_RISK_ENGINE_MUTATION_KEY = ['POST', 'DISABLE_RISK_ENGINE']; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_enable_risk_engine_mutation.ts b/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_enable_risk_engine_mutation.ts index 3875a79399dccf..40b3f1b4bddb68 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_enable_risk_engine_mutation.ts +++ b/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_enable_risk_engine_mutation.ts @@ -11,7 +11,7 @@ import { useInvalidateRiskEngineStatusQuery } from './use_risk_engine_status'; import type { EnableRiskEngineResponse, EnableDisableRiskEngineErrorResponse, -} from '../../../../server/lib/risk_engine/types'; +} from '../../../../server/lib/entity_analytics/risk_engine/types'; export const ENABLE_RISK_ENGINE_MUTATION_KEY = ['POST', 'ENABLE_RISK_ENGINE']; export const useEnableRiskEngineMutation = (options?: UseMutationOptions<{}>) => { diff --git a/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_init_risk_engine_mutation.ts b/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_init_risk_engine_mutation.ts index d220885148caca..35b1071b62b80f 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_init_risk_engine_mutation.ts +++ b/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_init_risk_engine_mutation.ts @@ -11,7 +11,7 @@ import { useInvalidateRiskEngineStatusQuery } from './use_risk_engine_status'; import type { InitRiskEngineResponse, InitRiskEngineError, -} from '../../../../server/lib/risk_engine/types'; +} from '../../../../server/lib/entity_analytics/risk_engine/types'; export const INIT_RISK_ENGINE_STATUS_KEY = ['POST', 'INIT_RISK_ENGINE']; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/risk_score_enable_section.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/risk_score_enable_section.tsx index 0eead8683e4f29..bd0b0e262e3cc1 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/risk_score_enable_section.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/risk_score_enable_section.tsx @@ -235,11 +235,11 @@ export const RiskScoreEnableSection = () => { let initRiskEngineErrors: string[] = []; if (initRiskEngineMutation.isError) { - const errorBody = initRiskEngineMutation.error.body.message; + const errorBody = initRiskEngineMutation.error.body; if (errorBody?.full_error?.errors) { initRiskEngineErrors = errorBody.full_error?.errors; } else { - initRiskEngineErrors = [errorBody]; + initRiskEngineErrors = [errorBody.message]; } } @@ -266,10 +266,10 @@ export const RiskScoreEnableSection = () => { {initRiskEngineMutation.isError && } {disableRiskEngineMutation.isError && ( - + )} {enableRiskEngineMutation.isError && ( - + )} diff --git a/x-pack/plugins/security_solution/public/management/components/console/mocks.tsx b/x-pack/plugins/security_solution/public/management/components/console/mocks.tsx index ac729f898c6be6..d756e6aaea2e56 100644 --- a/x-pack/plugins/security_solution/public/management/components/console/mocks.tsx +++ b/x-pack/plugins/security_solution/public/management/components/console/mocks.tsx @@ -10,8 +10,7 @@ import React, { memo, useEffect } from 'react'; import { EuiCode } from '@elastic/eui'; import userEvent from '@testing-library/user-event'; -import { act } from '@testing-library/react'; -import { within } from '@testing-library/dom'; +import { act, within } from '@testing-library/react'; import { convertToTestId } from './components/command_list'; import { Console } from './console'; import type { diff --git a/x-pack/plugins/security_solution/public/management/cypress/README.md b/x-pack/plugins/security_solution/public/management/cypress/README.md index 65af201662c48f..79689e650faea9 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/README.md +++ b/x-pack/plugins/security_solution/public/management/cypress/README.md @@ -36,6 +36,7 @@ Similarly to Security Solution cypress tests, we use tags in order to select whi - `@serverless` includes a test in the Serverless test suite. You need to explicitly add this tag to any test you want to run against a Serverless environment. - `@ess` includes a test in the normal, non-Serverless test suite. You need to explicitly add this tag to any test you want to run against a non-Serverless environment. - `@brokenInServerless` excludes a test from the Serverless test suite (even if it's tagged as `@serverless`). Indicates that a test should run in Serverless, but currently is broken. +- `@skipInServerless` excludes a test from the Serverless test suite (even if it's tagged as `@serverless`). Indicates that we don't want to run the given test in Serverless. Important: if you don't provide any tag, your test won't be executed. diff --git a/x-pack/plugins/security_solution/public/management/cypress/cypress_serverless.config.ts b/x-pack/plugins/security_solution/public/management/cypress/cypress_serverless.config.ts index 35dda2bd68501c..77507e12fae876 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/cypress_serverless.config.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/cypress_serverless.config.ts @@ -14,7 +14,7 @@ export default defineCypressConfig( env: { IS_SERVERLESS: true, - grepTags: '@serverless --@brokenInServerless', + grepTags: '@serverless --@brokenInServerless --@skipInServerless', }, }) ); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/artifacts/artifact_tabs_in_policy_details.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/artifacts/artifact_tabs_in_policy_details.cy.ts index 5988b73852af5c..b6040691c485f4 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/artifacts/artifact_tabs_in_policy_details.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/artifacts/artifact_tabs_in_policy_details.cy.ts @@ -16,9 +16,10 @@ import { removeExceptionsList, yieldFirstPolicyID, } from '../../tasks/artifacts'; -import { loadEndpointDataForEventFiltersIfNeeded } from '../../tasks/load_endpoint_data'; import { login, ROLE } from '../../tasks/login'; import { performUserActions } from '../../tasks/perform_user_actions'; +import { indexEndpointHosts } from '../../tasks/index_endpoint_hosts'; +import type { ReturnTypeFromChainable } from '../../types'; const loginWithPrivilegeAll = () => { login(ROLE.endpoint_policy_manager); @@ -58,15 +59,20 @@ const visitArtifactTab = (tabId: string) => { cy.get(`#${tabId}`).click(); }; -describe('Artifact tabs in Policy Details page', { tags: ['@ess'] }, () => { +describe('Artifact tabs in Policy Details page', { tags: ['@ess', '@serverless'] }, () => { + let endpointData: ReturnTypeFromChainable | undefined; + before(() => { - login(); - loadEndpointDataForEventFiltersIfNeeded(); + indexEndpointHosts().then((indexEndpoints) => { + endpointData = indexEndpoints; + }); }); after(() => { - login(); removeAllArtifacts(); + + endpointData?.cleanup(); + endpointData = undefined; }); for (const testData of getArtifactsListTestsData()) { @@ -76,22 +82,32 @@ describe('Artifact tabs in Policy Details page', { tags: ['@ess'] }, () => { removeExceptionsList(testData.createRequestBody.list_id); }); - it(`[NONE] User cannot see the tab for ${testData.title}`, () => { - loginWithPrivilegeNone(testData.privilegePrefix); - visitPolicyDetailsPage(); + it( + `[NONE] User cannot see the tab for ${testData.title}`, + // there is no such role in Serverless environment that can read policy but cannot read artifacts + { tags: ['@skipInServerless'] }, + () => { + loginWithPrivilegeNone(testData.privilegePrefix); + visitPolicyDetailsPage(); - cy.get(`#${testData.tabId}`).should('not.exist'); - }); + cy.get(`#${testData.tabId}`).should('not.exist'); + } + ); context(`Given there are no ${testData.title} entries`, () => { - it(`[READ] User CANNOT add ${testData.title} artifact`, () => { - loginWithPrivilegeRead(testData.privilegePrefix); - visitArtifactTab(testData.tabId); + it( + `[READ] User CANNOT add ${testData.title} artifact`, + // there is no such role in Serverless environment that only reads artifacts + { tags: ['@skipInServerless'] }, + () => { + loginWithPrivilegeRead(testData.privilegePrefix); + visitArtifactTab(testData.tabId); - cy.getByTestSubj('policy-artifacts-empty-unexisting').should('exist'); + cy.getByTestSubj('policy-artifacts-empty-unexisting').should('exist'); - cy.getByTestSubj('unexisting-manage-artifacts-button').should('not.exist'); - }); + cy.getByTestSubj('unexisting-manage-artifacts-button').should('not.exist'); + } + ); it(`[ALL] User can add ${testData.title} artifact`, () => { loginWithPrivilegeAll(); @@ -129,15 +145,20 @@ describe('Artifact tabs in Policy Details page', { tags: ['@ess'] }, () => { createPerPolicyArtifact(testData.artifactName, testData.createRequestBody); }); - it(`[READ] User CANNOT Manage or Assign ${testData.title} artifacts`, () => { - loginWithPrivilegeRead(testData.privilegePrefix); - visitArtifactTab(testData.tabId); + it( + `[READ] User CANNOT Manage or Assign ${testData.title} artifacts`, + // there is no such role in Serverless environment that only reads artifacts + { tags: ['@skipInServerless'] }, + () => { + loginWithPrivilegeRead(testData.privilegePrefix); + visitArtifactTab(testData.tabId); - cy.getByTestSubj('policy-artifacts-empty-unassigned').should('exist'); + cy.getByTestSubj('policy-artifacts-empty-unassigned').should('exist'); - cy.getByTestSubj('unassigned-manage-artifacts-button').should('not.exist'); - cy.getByTestSubj('unassigned-assign-artifacts-button').should('not.exist'); - }); + cy.getByTestSubj('unassigned-manage-artifacts-button').should('not.exist'); + cy.getByTestSubj('unassigned-assign-artifacts-button').should('not.exist'); + } + ); it(`[ALL] User can Manage and Assign ${testData.title} artifacts`, () => { loginWithPrivilegeAll(); @@ -173,23 +194,28 @@ describe('Artifact tabs in Policy Details page', { tags: ['@ess'] }, () => { }); }); - it(`[READ] User can see ${testData.title} artifacts but CANNOT assign or remove from policy`, () => { - loginWithPrivilegeRead(testData.privilegePrefix); - visitArtifactTab(testData.tabId); - - // List of artifacts - cy.getByTestSubj('artifacts-collapsed-list-card').should('have.length', 1); - cy.getByTestSubj('artifacts-collapsed-list-card-header-titleHolder').contains( - testData.artifactName - ); - - // Cannot assign artifacts - cy.getByTestSubj('artifacts-assign-button').should('not.exist'); - - // Cannot remove from policy - cy.getByTestSubj('artifacts-collapsed-list-card-header-actions-button').click(); - cy.getByTestSubj('remove-from-policy-action').should('not.exist'); - }); + it( + `[READ] User can see ${testData.title} artifacts but CANNOT assign or remove from policy`, + // there is no such role in Serverless environment that only reads artifacts + { tags: ['@skipInServerless'] }, + () => { + loginWithPrivilegeRead(testData.privilegePrefix); + visitArtifactTab(testData.tabId); + + // List of artifacts + cy.getByTestSubj('artifacts-collapsed-list-card').should('have.length', 1); + cy.getByTestSubj('artifacts-collapsed-list-card-header-titleHolder').contains( + testData.artifactName + ); + + // Cannot assign artifacts + cy.getByTestSubj('artifacts-assign-button').should('not.exist'); + + // Cannot remove from policy + cy.getByTestSubj('artifacts-collapsed-list-card-header-actions-button').click(); + cy.getByTestSubj('remove-from-policy-action').should('not.exist'); + } + ); it(`[ALL] User can see ${testData.title} artifacts and can assign or remove artifacts from policy`, () => { loginWithPrivilegeAll(); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/artifacts/artifacts_mocked_data.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/artifacts/artifacts_mocked_data.cy.ts index 807502f93880c2..e7f32820c00d7d 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/artifacts/artifacts_mocked_data.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/artifacts/artifacts_mocked_data.cy.ts @@ -12,7 +12,8 @@ import { loadPage } from '../../tasks/common'; import { getArtifactsListTestsData } from '../../fixtures/artifacts_page'; import { removeAllArtifacts } from '../../tasks/artifacts'; import { performUserActions } from '../../tasks/perform_user_actions'; -import { loadEndpointDataForEventFiltersIfNeeded } from '../../tasks/load_endpoint_data'; +import { indexEndpointHosts } from '../../tasks/index_endpoint_hosts'; +import type { ReturnTypeFromChainable } from '../../types'; const loginWithWriteAccess = (url: string) => { login(ROLE.endpoint_policy_manager); @@ -30,17 +31,22 @@ const loginWithoutAccess = (url: string) => { loadPage(url); }; -describe('Artifacts pages', { tags: ['@ess'] }, () => { +describe('Artifacts pages', { tags: ['@ess', '@serverless'] }, () => { + let endpointData: ReturnTypeFromChainable | undefined; + before(() => { - login(); - loadEndpointDataForEventFiltersIfNeeded(); - // Clean artifacts data + indexEndpointHosts().then((indexEndpoints) => { + endpointData = indexEndpoints; + }); + removeAllArtifacts(); }); after(() => { - // Clean artifacts data removeAllArtifacts(); + + endpointData?.cleanup(); + endpointData = undefined; }); for (const testData of getArtifactsListTestsData()) { @@ -53,14 +59,19 @@ describe('Artifacts pages', { tags: ['@ess'] }, () => { cy.getByTestSubj(`${testData.pagePrefix}-emptyState-addButton`).should('not.exist'); }); - it(`read - should show empty state page if there is no ${testData.title} entry and the add button does not exist`, () => { - loginWithReadAccess( - testData.privilegePrefix, - `/app/security/administration/${testData.urlPath}` - ); - cy.getByTestSubj(testData.emptyState).should('exist'); - cy.getByTestSubj(`${testData.pagePrefix}-emptyState-addButton`).should('not.exist'); - }); + it( + `read - should show empty state page if there is no ${testData.title} entry and the add button does not exist`, + // there is no such role in Serverless environment that only reads artifacts + { tags: ['@skipInServerless'] }, + () => { + loginWithReadAccess( + testData.privilegePrefix, + `/app/security/administration/${testData.urlPath}` + ); + cy.getByTestSubj(testData.emptyState).should('exist'); + cy.getByTestSubj(`${testData.pagePrefix}-emptyState-addButton`).should('not.exist'); + } + ); it(`write - should show empty state page if there is no ${testData.title} entry and the add button exists`, () => { loginWithWriteAccess(`/app/security/administration/${testData.urlPath}`); @@ -87,25 +98,35 @@ describe('Artifacts pages', { tags: ['@ess'] }, () => { cy.getByTestSubj('header-page-title').contains(testData.title); }); - it(`read - should not be able to update/delete an existing ${testData.title} entry`, () => { - loginWithReadAccess( - testData.privilegePrefix, - `/app/security/administration/${testData.urlPath}` - ); - cy.getByTestSubj('header-page-title').contains(testData.title); - cy.getByTestSubj(`${testData.pagePrefix}-card-header-actions-button`).should('not.exist'); - cy.getByTestSubj(`${testData.pagePrefix}-card-cardEditAction`).should('not.exist'); - cy.getByTestSubj(`${testData.pagePrefix}-card-cardDeleteAction`).should('not.exist'); - }); - - it(`read - should not be able to create a new ${testData.title} entry`, () => { - loginWithReadAccess( - testData.privilegePrefix, - `/app/security/administration/${testData.urlPath}` - ); - cy.getByTestSubj('header-page-title').contains(testData.title); - cy.getByTestSubj(`${testData.pagePrefix}-pageAddButton`).should('not.exist'); - }); + it( + `read - should not be able to update/delete an existing ${testData.title} entry`, + // there is no such role in Serverless environment that only reads artifacts + { tags: ['@skipInServerless'] }, + () => { + loginWithReadAccess( + testData.privilegePrefix, + `/app/security/administration/${testData.urlPath}` + ); + cy.getByTestSubj('header-page-title').contains(testData.title); + cy.getByTestSubj(`${testData.pagePrefix}-card-header-actions-button`).should('not.exist'); + cy.getByTestSubj(`${testData.pagePrefix}-card-cardEditAction`).should('not.exist'); + cy.getByTestSubj(`${testData.pagePrefix}-card-cardDeleteAction`).should('not.exist'); + } + ); + + it( + `read - should not be able to create a new ${testData.title} entry`, + // there is no such role in Serverless environment that only reads artifacts + { tags: ['@skipInServerless'] }, + () => { + loginWithReadAccess( + testData.privilegePrefix, + `/app/security/administration/${testData.urlPath}` + ); + cy.getByTestSubj('header-page-title').contains(testData.title); + cy.getByTestSubj(`${testData.pagePrefix}-pageAddButton`).should('not.exist'); + } + ); it(`write - should be able to update an existing ${testData.title} entry`, () => { loginWithWriteAccess(`/app/security/administration/${testData.urlPath}`); 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 1948434b39c9f5..50bcefc65ab956 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 @@ -20,7 +20,8 @@ import { createEndpointHost } from '../../tasks/create_endpoint_host'; import { deleteAllLoadedEndpointData } from '../../tasks/delete_all_endpoint_data'; import { enableAllPolicyProtections } from '../../tasks/endpoint_policy'; -describe( +// FLAKY: https://github.com/elastic/kibana/issues/168340 +describe.skip( 'Automated Response Actions', { tags: [ 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 a370f2a89cb6f4..fb1285fb89f05b 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 @@ -15,7 +15,7 @@ import { visitRuleActions, } from '../../tasks/response_actions'; import { cleanupRule, generateRandomStringName, loadRule } from '../../tasks/api_fixtures'; -import { RESPONSE_ACTION_TYPES } from '../../../../../common/api/detection_engine'; +import { ResponseActionTypesEnum } from '../../../../../common/api/detection_engine'; import { login, ROLE } from '../../tasks/login'; describe( @@ -78,7 +78,7 @@ describe( cy.getByTestSubj(`command-type-${testedCommand}`).click(); cy.intercept('POST', '/api/detection_engine/rules', (request) => { const result = { - action_type_id: RESPONSE_ACTION_TYPES.ENDPOINT, + action_type_id: ResponseActionTypesEnum['.endpoint'], params: { command: testedCommand, comment: 'example1', @@ -127,7 +127,7 @@ describe( cy.getByTestSubj('ruleEditSubmitButton').click(); cy.wait('@updateResponseAction').should(({ request }) => { const query = { - action_type_id: RESPONSE_ACTION_TYPES.ENDPOINT, + action_type_id: ResponseActionTypesEnum['.endpoint'], params: { command: testedCommand, comment: newDescription, diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/policy/policy_details.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/policy/policy_details.cy.ts index 3cfa2a5c9287d3..ae79f3d47b4a56 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/policy/policy_details.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/policy/policy_details.cy.ts @@ -39,8 +39,8 @@ describe( describe('Renders and saves protection updates', () => { let indexedPolicy: IndexedFleetEndpointPolicyResponse; let policy: PolicyData; - const today = moment.utc(); - const formattedToday = today.format('MMMM DD, YYYY'); + const defaultDate = moment.utc().subtract(1, 'days'); + const formattedDefaultDate = defaultDate.format('MMMM DD, YYYY'); beforeEach(() => { login(); @@ -73,7 +73,7 @@ describe( cy.getByTestSubj('protection-updates-deployed-version').contains('latest'); cy.getByTestSubj('protection-updates-manifest-name-version-to-deploy-title'); cy.getByTestSubj('protection-updates-version-to-deploy-picker').within(() => { - cy.get('input').should('have.value', formattedToday); + cy.get('input').should('have.value', formattedDefaultDate); }); cy.getByTestSubj('protection-updates-manifest-name-note-title'); cy.getByTestSubj('protection-updates-manifest-note'); @@ -91,7 +91,7 @@ describe( cy.getByTestSubj('protectionUpdatesSaveButton').click(); cy.wait('@policy').then(({ request, response }) => { expect(request.body.inputs[0].config.policy.value.global_manifest_version).to.equal( - today.format('YYYY-MM-DD') + defaultDate.format('YYYY-MM-DD') ); expect(response?.statusCode).to.equal(200); }); @@ -102,7 +102,7 @@ describe( }); cy.getByTestSubj('protectionUpdatesSuccessfulMessage'); - cy.getByTestSubj('protection-updates-deployed-version').contains(formattedToday); + cy.getByTestSubj('protection-updates-deployed-version').contains(formattedDefaultDate); cy.getByTestSubj('protection-updates-manifest-note').contains(testNote); cy.getByTestSubj('protectionUpdatesSaveButton').should('be.disabled'); }); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/alerts_response_console.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/alerts_response_console.cy.ts index a736e05c331451..00dc41b94ef4f5 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/alerts_response_console.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/alerts_response_console.cy.ts @@ -26,7 +26,8 @@ import { enableAllPolicyProtections } from '../../tasks/endpoint_policy'; import { createEndpointHost } from '../../tasks/create_endpoint_host'; import { deleteAllLoadedEndpointData } from '../../tasks/delete_all_endpoint_data'; -describe('Response console', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => { +// FLAKY: https://github.com/elastic/kibana/issues/169689 +describe.skip('Response console', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => { let indexedPolicy: IndexedFleetEndpointPolicyResponse; let policy: PolicyData; let createdHost: CreateAndEnrollEndpointHostResponse; diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/document_signing.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/document_signing.cy.ts index b8063237260185..4093581366321e 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/document_signing.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/document_signing.cy.ts @@ -22,7 +22,8 @@ import { enableAllPolicyProtections } from '../../tasks/endpoint_policy'; import { createEndpointHost } from '../../tasks/create_endpoint_host'; import { deleteAllLoadedEndpointData } from '../../tasks/delete_all_endpoint_data'; -describe('Document signing:', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => { +// FLAKY: https://github.com/elastic/kibana/issues/170674 +describe.skip('Document signing:', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => { let indexedPolicy: IndexedFleetEndpointPolicyResponse; let policy: PolicyData; let createdHost: CreateAndEnrollEndpointHostResponse; diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/endpoints_list_response_console.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/endpoints_list_response_console.cy.ts index 75074b0d3f94ab..3d02bc14251dd0 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/endpoints_list_response_console.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/endpoints_list_response_console.cy.ts @@ -20,7 +20,8 @@ import { enableAllPolicyProtections } from '../../tasks/endpoint_policy'; import { createEndpointHost } from '../../tasks/create_endpoint_host'; import { deleteAllLoadedEndpointData } from '../../tasks/delete_all_endpoint_data'; -describe('Response console', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => { +// FLAKY: https://github.com/elastic/kibana/issues/169821 +describe.skip('Response console', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => { beforeEach(() => { login(); }); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/response_console/execute.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/response_console/execute.cy.ts index d43037f4d7f978..dad573bb09c2bf 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/response_console/execute.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/response_console/execute.cy.ts @@ -21,8 +21,7 @@ import { enableAllPolicyProtections } from '../../../tasks/endpoint_policy'; import { createEndpointHost } from '../../../tasks/create_endpoint_host'; import { deleteAllLoadedEndpointData } from '../../../tasks/delete_all_endpoint_data'; -// FLAKY: https://github.com/elastic/kibana/issues/170373 -describe.skip('Response console', { tags: ['@ess', '@serverless'] }, () => { +describe('Response console', { tags: ['@ess', '@serverless'] }, () => { beforeEach(() => { login(); }); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/policy_details_with_security_essentials.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/policy_details_with_security_essentials.cy.ts index efb48f65435425..36ebb060454c5a 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/policy_details_with_security_essentials.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/policy_details_with_security_essentials.cy.ts @@ -9,8 +9,7 @@ import { login } from '../../tasks/login'; import { visitPolicyDetailsPage } from '../../screens/policy_details'; import type { IndexedFleetEndpointPolicyResponse } from '../../../../../common/endpoint/data_loaders/index_fleet_endpoint_policy'; -// FLAKY: https://github.com/elastic/kibana/issues/170666 -describe.skip( +describe( 'When displaying the Policy Details in Security Essentials PLI', { tags: ['@serverless'], @@ -24,7 +23,11 @@ describe.skip( let loadedPolicyData: IndexedFleetEndpointPolicyResponse; before(() => { - cy.task('indexFleetEndpointPolicy', { policyName: 'tests-serverless' }).then((response) => { + cy.task( + 'indexFleetEndpointPolicy', + { policyName: 'tests-serverless' }, + { timeout: 5 * 60 * 1000 } + ).then((response) => { loadedPolicyData = response as IndexedFleetEndpointPolicyResponse; }); }); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/tamper_protection/disabled/uninstall_agent_from_host.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/tamper_protection/disabled/uninstall_agent_from_host.cy.ts index ed47855ac894a6..34aba3fcfccf2e 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/tamper_protection/disabled/uninstall_agent_from_host.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/tamper_protection/disabled/uninstall_agent_from_host.cy.ts @@ -21,7 +21,8 @@ import { enableAllPolicyProtections } from '../../../tasks/endpoint_policy'; import { createEndpointHost } from '../../../tasks/create_endpoint_host'; import { deleteAllLoadedEndpointData } from '../../../tasks/delete_all_endpoint_data'; -describe( +// FLAKY: https://github.com/elastic/kibana/issues/170667 +describe.skip( 'Uninstall agent from host when agent tamper protection is disabled', { tags: ['@ess'] }, () => { diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/tamper_protection/enabled/unenroll_agent_from_fleet.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/tamper_protection/enabled/unenroll_agent_from_fleet.cy.ts index 17cb52c2cb0424..f2aef24ad5e12f 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/tamper_protection/enabled/unenroll_agent_from_fleet.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/tamper_protection/enabled/unenroll_agent_from_fleet.cy.ts @@ -20,7 +20,8 @@ import { login } from '../../../tasks/login'; import { createEndpointHost } from '../../../tasks/create_endpoint_host'; import { deleteAllLoadedEndpointData } from '../../../tasks/delete_all_endpoint_data'; -describe( +// FLAKY: https://github.com/elastic/kibana/issues/170706 +describe.skip( 'Unenroll agent from fleet when agent tamper protection is enabled', { tags: ['@ess'] }, () => { diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/tamper_protection/enabled/uninstall_agent_from_host.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/tamper_protection/enabled/uninstall_agent_from_host.cy.ts index 527566bed608b1..8f45e3d70b5e63 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/tamper_protection/enabled/uninstall_agent_from_host.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/tamper_protection/enabled/uninstall_agent_from_host.cy.ts @@ -22,7 +22,8 @@ import { login } from '../../../tasks/login'; import { createEndpointHost } from '../../../tasks/create_endpoint_host'; import { deleteAllLoadedEndpointData } from '../../../tasks/delete_all_endpoint_data'; -describe( +// FLAKY: https://github.com/elastic/kibana/issues/170601 +describe.skip( 'Uninstall agent from host when agent tamper protection is enabled', { tags: ['@ess'] }, () => { diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/tamper_protection/switching_policies/unenroll_agent_from_fleet_changing_policy_from_disabled_to_enabled.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/tamper_protection/switching_policies/unenroll_agent_from_fleet_changing_policy_from_disabled_to_enabled.cy.ts index d30345d8d54868..b03608c9c52f2f 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/tamper_protection/switching_policies/unenroll_agent_from_fleet_changing_policy_from_disabled_to_enabled.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/tamper_protection/switching_policies/unenroll_agent_from_fleet_changing_policy_from_disabled_to_enabled.cy.ts @@ -22,7 +22,8 @@ import { enableAllPolicyProtections } from '../../../tasks/endpoint_policy'; import { createEndpointHost } from '../../../tasks/create_endpoint_host'; import { deleteAllLoadedEndpointData } from '../../../tasks/delete_all_endpoint_data'; -describe( +// FLAKY: https://github.com/elastic/kibana/issues/170811 +describe.skip( 'Unenroll agent from fleet when agent tamper protection is disabled but then is switched to a policy with it enabled', { tags: ['@ess'] }, () => { diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/tamper_protection/switching_policies/uninstall_agent_from_host_changing_policy_from_disabled_to_enabled.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/tamper_protection/switching_policies/uninstall_agent_from_host_changing_policy_from_disabled_to_enabled.cy.ts index 5950288f2313e5..056191ad2bd7fe 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/tamper_protection/switching_policies/uninstall_agent_from_host_changing_policy_from_disabled_to_enabled.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/tamper_protection/switching_policies/uninstall_agent_from_host_changing_policy_from_disabled_to_enabled.cy.ts @@ -24,7 +24,8 @@ import { enableAllPolicyProtections } from '../../../tasks/endpoint_policy'; import { createEndpointHost } from '../../../tasks/create_endpoint_host'; import { deleteAllLoadedEndpointData } from '../../../tasks/delete_all_endpoint_data'; -describe( +// FLAKY: https://github.com/elastic/kibana/issues/170794 +describe.skip( 'Uninstall agent from host changing agent policy when agent tamper protection is disabled but then is switched to a policy with it enabled', { tags: ['@ess'] }, () => { diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/tamper_protection/switching_policies/uninstall_agent_from_host_changing_policy_from_enabled_to_disabled.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/tamper_protection/switching_policies/uninstall_agent_from_host_changing_policy_from_enabled_to_disabled.cy.ts index e55872351aef06..e949851677f2a1 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/tamper_protection/switching_policies/uninstall_agent_from_host_changing_policy_from_enabled_to_disabled.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/tamper_protection/switching_policies/uninstall_agent_from_host_changing_policy_from_enabled_to_disabled.cy.ts @@ -23,7 +23,8 @@ import { enableAllPolicyProtections } from '../../../tasks/endpoint_policy'; import { createEndpointHost } from '../../../tasks/create_endpoint_host'; import { deleteAllLoadedEndpointData } from '../../../tasks/delete_all_endpoint_data'; -describe( +// FLAKY: https://github.com/elastic/kibana/issues/170604 +describe.skip( 'Uninstall agent from host changing agent policy when agent tamper protection is enabled but then is switched to a policy with it disabled', { tags: ['@ess'] }, () => { diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/tamper_protection/switching_policies/uninstall_agent_from_host_changing_policy_from_enabled_to_enabled.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/tamper_protection/switching_policies/uninstall_agent_from_host_changing_policy_from_enabled_to_enabled.cy.ts index 15fd02ad14511f..6dca6af07a53ab 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/tamper_protection/switching_policies/uninstall_agent_from_host_changing_policy_from_enabled_to_enabled.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/tamper_protection/switching_policies/uninstall_agent_from_host_changing_policy_from_enabled_to_enabled.cy.ts @@ -23,7 +23,8 @@ import { login } from '../../../tasks/login'; import { createEndpointHost } from '../../../tasks/create_endpoint_host'; import { deleteAllLoadedEndpointData } from '../../../tasks/delete_all_endpoint_data'; -describe( +// FLAKY: https://github.com/elastic/kibana/issues/170812 +describe.skip( 'Uninstall agent from host changing agent policy when agent tamper protection is enabled but then is switched to a policy with it also enabled', { tags: ['@ess'] }, () => { diff --git a/x-pack/plugins/security_solution/public/management/cypress/tasks/fleet.ts b/x-pack/plugins/security_solution/public/management/cypress/tasks/fleet.ts index 8f1da4a0ec020c..bc0f94d7120584 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/tasks/fleet.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/tasks/fleet.ts @@ -81,15 +81,20 @@ export const yieldEndpointPolicyRevision = (): Cypress.Chainable => export const createAgentPolicyTask = ( version: string, - policyPrefix?: string + policyPrefix?: string, + timeout?: number ): Cypress.Chainable => { const policyName = `${policyPrefix || 'Reassign'} ${Math.random().toString(36).substring(2, 7)}`; - return cy.task('indexFleetEndpointPolicy', { - policyName, - endpointPackageVersion: version, - agentPolicyName: policyName, - }); + return cy.task( + 'indexFleetEndpointPolicy', + { + policyName, + endpointPackageVersion: version, + agentPolicyName: policyName, + }, + { timeout: timeout ?? 5 * 60 * 1000 } + ); }; export const enableAgentTamperProtectionFeatureFlagInPolicy = (agentPolicyId: string) => { diff --git a/x-pack/plugins/security_solution/public/management/cypress/tasks/load_endpoint_data.ts b/x-pack/plugins/security_solution/public/management/cypress/tasks/load_endpoint_data.ts deleted file mode 100644 index b8029a8528e94b..00000000000000 --- a/x-pack/plugins/security_solution/public/management/cypress/tasks/load_endpoint_data.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { isEmpty } from 'lodash'; -import { BASE_ENDPOINT_ROUTE } from '../../../../common/endpoint/constants'; -import { runEndpointLoaderScript } from './run_endpoint_loader'; -import { request } from './common'; - -// Checks for Endpoint data and creates it if needed -export const loadEndpointDataForEventFiltersIfNeeded = () => { - request({ - method: 'POST', - url: `${BASE_ENDPOINT_ROUTE}/suggestions/eventFilters`, - body: { - field: 'agent.type', - query: '', - }, - failOnStatusCode: false, - }).then(({ body }) => { - if (isEmpty(body)) { - runEndpointLoaderScript(); - } - }); -}; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/integration_tests/search_bar.test.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/integration_tests/search_bar.test.tsx index ab0a856ff1fa65..f053ee5ef8d682 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/integration_tests/search_bar.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/integration_tests/search_bar.test.tsx @@ -9,10 +9,9 @@ import React from 'react'; import type { AppContextTestRender } from '../../../../../../common/mock/endpoint'; import { createAppRootMockRenderer } from '../../../../../../common/mock/endpoint'; import { endpointPageHttpMock } from '../../../mocks'; -import { act, waitFor, cleanup } from '@testing-library/react'; +import { act, waitFor, cleanup, fireEvent } from '@testing-library/react'; import { getEndpointListPath } from '../../../../../common/routing'; import { AdminSearchBar } from '../search_bar'; -import { fireEvent } from '@testing-library/dom'; import { uiQueryParams } from '../../../store/selectors'; import type { EndpointIndexUIQueryParams } from '../../../types'; diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form.test.tsx b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form.test.tsx index be028512cb0a85..2870a67e7042cb 100644 --- a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form.test.tsx @@ -25,6 +25,7 @@ import { OperatingSystem } from '@kbn/securitysolution-utils'; import { EventFiltersForm } from './form'; import { EndpointDocGenerator } from '../../../../../../common/endpoint/generate_data'; import type { PolicyData } from '../../../../../../common/endpoint/types'; +import { MAX_COMMENT_LENGTH } from '../../../../../../common/constants'; jest.mock('../../../../../common/lib/kibana'); jest.mock('../../../../../common/containers/source'); @@ -466,4 +467,35 @@ describe('Event filter form', () => { expect(renderResult.findByTestId('duplicate-fields-warning-message')).not.toBeNull(); }); }); + + describe('Errors', () => { + beforeEach(() => { + render(); + }); + + it('should not show warning text when unique fields are added', async () => { + rerender(); + + const commentInput = renderResult.getByLabelText('Comment Input'); + + expect( + renderResult.queryByText( + `The length of the comment is too long. The maximum length is ${MAX_COMMENT_LENGTH} characters.` + ) + ).toBeNull(); + act(() => { + fireEvent.change(commentInput, { + target: { + value: [...new Array(MAX_COMMENT_LENGTH + 1).keys()].map((_) => 'a').join(''), + }, + }); + fireEvent.blur(commentInput); + }); + expect( + renderResult.queryByText( + `The length of the comment is too long. The maximum length is ${MAX_COMMENT_LENGTH} characters.` + ) + ).not.toBeNull(); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form.tsx b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form.tsx index 3b4ff4e394a826..e4e1fa7e146381 100644 --- a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form.tsx @@ -133,6 +133,7 @@ export const EventFiltersForm: React.FC(!exception.name); const [newComment, setNewComment] = useState(''); + const [hasCommentError, setHasCommentError] = useState(false); const [hasBeenInputNameVisited, setHasBeenInputNameVisited] = useState(false); const [selectedPolicies, setSelectedPolicies] = useState([]); const isPlatinumPlus = useLicense().isPlatinumPlus(); @@ -173,10 +174,11 @@ export const EventFiltersForm: React.FC e.value !== '' || e.value.length) ); - }, [hasNameError, exception.entries]); + }, [hasCommentError, hasNameError, exception.entries]); const processChanged = useCallback( (updatedItem?: Partial) => { @@ -340,6 +342,7 @@ export const EventFiltersForm: React.FC ), [existingComments, handleOnChangeComment, newComment] diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/assignable/policy_artifacts_assignable_list.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/assignable/policy_artifacts_assignable_list.test.tsx index aa57df82276c6b..c1f95b1f4d067f 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/assignable/policy_artifacts_assignable_list.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/assignable/policy_artifacts_assignable_list.test.tsx @@ -10,7 +10,7 @@ import { PolicyArtifactsAssignableList } from './policy_artifacts_assignable_lis import * as reactTestingLibrary from '@testing-library/react'; import type { AppContextTestRender } from '../../../../../../common/mock/endpoint'; import { createAppRootMockRenderer } from '../../../../../../common/mock/endpoint'; -import { fireEvent } from '@testing-library/dom'; +import { fireEvent } from '@testing-library/react'; import { getMockListResponse } from '../../../test_utils'; describe('Policy artifacts list', () => { diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/advanced_section.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/advanced_section.test.tsx index 0eb65818b93475..6688c12c7f8530 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/advanced_section.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/advanced_section.test.tsx @@ -17,7 +17,7 @@ import type { AdvancedSectionProps } from './advanced_section'; import { AdvancedSection } from './advanced_section'; import userEvent from '@testing-library/user-event'; import { AdvancedPolicySchema } from '../../../models/advanced_policy_schema'; -import { within } from '@testing-library/dom'; +import { within } from '@testing-library/react'; import { set } from 'lodash'; jest.mock('../../../../../../common/hooks/use_license'); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/event_collection_card.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/event_collection_card.test.tsx index eeaa22aa14f6b7..54ade4d11b4ddb 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/event_collection_card.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/components/event_collection_card.test.tsx @@ -18,7 +18,7 @@ import { OperatingSystem } from '@kbn/securitysolution-utils'; import { expectIsViewOnly, exactMatchText } from '../mocks'; import userEvent from '@testing-library/user-event'; import { cloneDeep, set } from 'lodash'; -import { within } from '@testing-library/dom'; +import { within } from '@testing-library/react'; describe('Policy Event Collection Card common component', () => { let formProps: EventCollectionCardProps; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/protection_updates/protection_updates_layout.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/protection_updates/protection_updates_layout.tsx index cf7012f9019744..dc260fdb6449da 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/protection_updates/protection_updates_layout.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/protection_updates/protection_updates_layout.tsx @@ -73,7 +73,9 @@ export const ProtectionUpdatesLayout = React.memo( const [manifestVersion, setManifestVersion] = useState(deployedVersion); const today = moment.utc(); - const [selectedDate, setSelectedDate] = useState(today); + const defaultDate = today.clone().subtract(1, 'days'); + + const [selectedDate, setSelectedDate] = useState(defaultDate); const { data: fetchedNote, isLoading: getNoteInProgress } = useGetProtectionUpdatesNote({ packagePolicyId: _policy.id, @@ -181,24 +183,24 @@ export const ProtectionUpdatesLayout = React.memo( if (checked && !automaticUpdatesEnabled) { setManifestVersion('latest'); // Clear selected date on user enabling automatic updates - if (selectedDate !== today) { - setSelectedDate(today); + if (selectedDate !== defaultDate) { + setSelectedDate(defaultDate); } } else { setManifestVersion(selectedDate.format(internalDateFormat)); } }, - [automaticUpdatesEnabled, selectedDate, today] + [automaticUpdatesEnabled, selectedDate, defaultDate] ); const updateDatepickerSelectedDate = useCallback( (date: Moment | null) => { - if (date?.isAfter(cutoffDate) && date?.isSameOrBefore(today)) { - setSelectedDate(date || today); + if (date?.isAfter(cutoffDate) && date?.isSameOrBefore(defaultDate)) { + setSelectedDate(date || defaultDate); setManifestVersion(date?.format(internalDateFormat) || 'latest'); } }, - [cutoffDate, today] + [cutoffDate, defaultDate] ); const renderVersionToDeployPicker = () => { @@ -224,7 +226,7 @@ export const ProtectionUpdatesLayout = React.memo( popoverPlacement={'downCenter'} dateFormat={displayDateFormat} selected={selectedDate} - maxDate={today} + maxDate={defaultDate} minDate={cutoffDate} onChange={updateDatepickerSelectedDate} /> diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/index.test.tsx index 153b24dcf0bf84..cd0a21d99cdfed 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/index.test.tsx @@ -21,7 +21,7 @@ import { buildGlobalQuery } from '../helpers'; import type { QueryBarTimelineComponentProps } from '.'; import { QueryBarTimeline, getDataProviderFilter, TIMELINE_FILTER_DROP_AREA } from '.'; -import { waitFor } from '@testing-library/dom'; +import { waitFor } from '@testing-library/react'; const mockUiSettingsForFilterManager = coreMock.createStart().uiSettings; diff --git a/x-pack/plugins/security_solution/scripts/endpoint/common/roles_users/index.ts b/x-pack/plugins/security_solution/scripts/endpoint/common/roles_users/index.ts index b035f55bf1589e..7861ee4d6e0d57 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/common/roles_users/index.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/common/roles_users/index.ts @@ -22,6 +22,7 @@ import { import { getDetectionsEngineer } from './detections_engineer'; import { getWithResponseActionsRole } from './with_response_actions_role'; import { getNoResponseActionsRole } from './without_response_actions_role'; +import { getWithArtifactReadPrivilegesRole } from './with_artifact_read_privileges_role'; export * from './with_response_actions_role'; export * from './without_response_actions_role'; @@ -74,6 +75,7 @@ export const ENDPOINT_SECURITY_ROLE_NAMES = Object.freeze({ endpoint_response_actions_access: 'endpoint_response_actions_access', endpoint_response_actions_no_access: 'endpoint_response_actions_no_access', endpoint_security_policy_management_read: 'endpoint_security_policy_management_read', + artifact_read_privileges: 'artifact_read_privileges', }); export const getAllEndpointSecurityRoles = (): EndpointSecurityRoleDefinitions => { @@ -135,5 +137,9 @@ export const getAllEndpointSecurityRoles = (): EndpointSecurityRoleDefinitions = ...getEndpointSecurityPolicyManagementReadRole(), name: 'endpoint_security_policy_management_read', }, + artifact_read_privileges: { + ...getWithArtifactReadPrivilegesRole(), + name: 'artifact_read_privileges', + }, }; }; diff --git a/x-pack/plugins/security_solution/scripts/endpoint/endpoint_agent_runner/Vagrantfile b/x-pack/plugins/security_solution/scripts/endpoint/endpoint_agent_runner/Vagrantfile index 42080eab2984e2..a6b93e8de47b5a 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/endpoint_agent_runner/Vagrantfile +++ b/x-pack/plugins/security_solution/scripts/endpoint/endpoint_agent_runner/Vagrantfile @@ -5,6 +5,7 @@ cachedAgentFilename = ENV["CACHED_AGENT_FILENAME"] || '' Vagrant.configure("2") do |config| config.vm.hostname = hostname config.vm.box = 'ubuntu/jammy64' + config.vm.box_check_update = false config.vm.provider :virtualbox do |vb| vb.memory = 4096 diff --git a/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts b/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts index 3d586d1dfab1a1..7c861bf87f18f7 100644 --- a/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts +++ b/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts @@ -441,6 +441,8 @@ describe('ingest_integration tests ', () => { licenseEmitter.next(Enterprise); // set license level to enterprise }); + const validDateYesterday = moment.utc().subtract(1, 'day'); + it.each([ { date: 'invalid', @@ -457,13 +459,21 @@ describe('ingest_integration tests ', () => { }, { date: '2100-10-01', - message: 'Global manifest version cannot be in the future. UTC time.', + message: `Global manifest version cannot be in the future. Latest selectable date is ${validDateYesterday.format( + 'MMMM DD, YYYY' + )} UTC time.`, + }, + { + date: validDateYesterday.clone().add(1, 'day').format('YYYY-MM-DD'), + message: `Global manifest version cannot be in the future. Latest selectable date is ${validDateYesterday.format( + 'MMMM DD, YYYY' + )} UTC time.`, }, { date: 'latest', }, { - date: moment.utc().subtract(1, 'day').format('YYYY-MM-DD'), // Correct date + date: validDateYesterday.format('YYYY-MM-DD'), // Correct date }, ])( 'should return bad request for invalid endpoint package policy global manifest values', diff --git a/x-pack/plugins/security_solution/server/fleet_integration/handlers/validate_endpoint_package_policy.ts b/x-pack/plugins/security_solution/server/fleet_integration/handlers/validate_endpoint_package_policy.ts index d8f1a8afecd794..f63d07d890c2d8 100644 --- a/x-pack/plugins/security_solution/server/fleet_integration/handlers/validate_endpoint_package_policy.ts +++ b/x-pack/plugins/security_solution/server/fleet_integration/handlers/validate_endpoint_package_policy.ts @@ -29,9 +29,12 @@ export const validateEndpointPackagePolicy = (inputs: NewPackagePolicyInput[]) = 'Global manifest version is too far in the past. Please use either "latest" or a date within the last 18 months. The earliest valid date is October 1, 2023, in UTC time.' ); } - if (parsedDate.isAfter(moment.utc())) { + const minAllowedDate = moment.utc().subtract(1, 'day'); + if (parsedDate.isAfter(minAllowedDate)) { throw createManifestVersionError( - 'Global manifest version cannot be in the future. UTC time.' + `Global manifest version cannot be in the future. Latest selectable date is ${minAllowedDate.format( + 'MMMM DD, YYYY' + )} UTC time.` ); } } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/model/rule_assets/prebuilt_rule_asset.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/model/rule_assets/prebuilt_rule_asset.test.ts index 73350b48941dbf..0ec1d5580f40b0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/model/rule_assets/prebuilt_rule_asset.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/model/rule_assets/prebuilt_rule_asset.test.ts @@ -17,7 +17,7 @@ describe('Prebuilt rule asset schema', () => { const result = PrebuiltRuleAsset.safeParse(payload); expectParseError(result); expect(stringifyZodError(result.error)).toMatchInlineSnapshot( - `"name: Required, description: Required, risk_score: Required, severity: Required, Invalid input, rule_id: Required, version: Required"` + `"name: Required, description: Required, risk_score: Required, severity: Required, rule_id: Required, and 26 more"` ); }); @@ -40,7 +40,7 @@ describe('Prebuilt rule asset schema', () => { const result = PrebuiltRuleAsset.safeParse(payload); expectParseError(result); expect(stringifyZodError(result.error)).toMatchInlineSnapshot( - `"name: Required, description: Required, risk_score: Required, severity: Required, Invalid input, version: Required"` + `"name: Required, description: Required, risk_score: Required, severity: Required, version: Required, and 25 more"` ); }); @@ -176,7 +176,9 @@ describe('Prebuilt rule asset schema', () => { const result = PrebuiltRuleAsset.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", index.0: Expected string, received number, index.0: Expected string, received number, type: Invalid literal value, expected \\"saved_query\\", and 20 more"` + ); }); test('saved_query type can have filters with it', () => { @@ -198,7 +200,9 @@ describe('Prebuilt rule asset schema', () => { const result = PrebuiltRuleAsset.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", filters: Expected array, received string, filters: Expected array, received string, type: Invalid literal value, expected \\"saved_query\\", and 20 more"` + ); }); test('language validates with kuery', () => { @@ -231,7 +235,9 @@ describe('Prebuilt rule asset schema', () => { const result = PrebuiltRuleAsset.safeParse(payload); expectParseError(result); - expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"Invalid input"`); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", language: Invalid enum value. Expected 'kuery' | 'lucene', received 'something-made-up', type: Invalid literal value, expected \\"saved_query\\", saved_id: Required, and 19 more"` + ); }); test('max_signals cannot be negative', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_context.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_context.ts index e68e84acf60292..f65d788945debb 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_context.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_context.ts @@ -34,7 +34,7 @@ import type { import { getEndpointAuthzInitialStateMock } from '../../../../../common/endpoint/service/authz/mocks'; import type { EndpointAuthz } from '../../../../../common/endpoint/types/authz'; -import { riskEngineDataClientMock } from '../../../risk_engine/risk_engine_data_client.mock'; +import { riskEngineDataClientMock } from '../../../entity_analytics/risk_engine/risk_engine_data_client.mock'; export const createMockClients = () => { const core = coreMock.createRequestHandlerContext(); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_actions/route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_actions/route.test.ts index 428010033c5b2b..cd01a251a3c750 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_actions/route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_actions/route.test.ts @@ -486,7 +486,7 @@ describe('Perform bulk action route', () => { }); const result = server.validate(request); expect(result.badRequest).toHaveBeenCalledWith( - 'Invalid value "undefined" supplied to "action",Invalid value "undefined" supplied to "edit"' + 'action: Invalid literal value, expected "delete", action: Invalid literal value, expected "disable", action: Invalid literal value, expected "enable", action: Invalid literal value, expected "export", action: Invalid literal value, expected "duplicate", and 2 more' ); }); @@ -498,7 +498,7 @@ describe('Perform bulk action route', () => { }); const result = server.validate(request); expect(result.badRequest).toHaveBeenCalledWith( - 'Invalid value "unknown" supplied to "action",Invalid value "undefined" supplied to "edit"' + 'action: Invalid literal value, expected "delete", action: Invalid literal value, expected "disable", action: Invalid literal value, expected "enable", action: Invalid literal value, expected "export", action: Invalid literal value, expected "duplicate", and 2 more' ); }); @@ -531,7 +531,9 @@ describe('Perform bulk action route', () => { body: { ...getPerformBulkActionSchemaMock(), ids: 'test fake' }, }); const result = server.validate(request); - expect(result.badRequest).toHaveBeenCalledWith('Invalid value "test fake" supplied to "ids"'); + expect(result.badRequest).toHaveBeenCalledWith( + 'ids: Expected array, received string, action: Invalid literal value, expected "delete", ids: Expected array, received string, ids: Expected array, received string, action: Invalid literal value, expected "enable", and 7 more' + ); }); it('rejects payload if there is more than 100 ids in payload', async () => { @@ -577,7 +579,9 @@ describe('Perform bulk action route', () => { body: { ...getPerformBulkActionSchemaMock(), ids: [] }, }); const result = server.validate(request); - expect(result.badRequest).toHaveBeenCalledWith('Invalid value "[]" supplied to "ids"'); + expect(result.badRequest).toHaveBeenCalledWith( + 'ids: Array must contain at least 1 element(s)' + ); }); it('rejects payloads if property "edit" actions is empty', async () => { @@ -588,7 +592,7 @@ describe('Perform bulk action route', () => { }); const result = server.validate(request); expect(result.badRequest).toHaveBeenCalledWith( - expect.stringContaining('Invalid value "[]" supplied to "edit"') + expect.stringContaining('edit: Array must contain at least 1 element(s)') ); }); @@ -601,7 +605,9 @@ describe('Perform bulk action route', () => { }); const result = server.validate(request); expect(result.badRequest).toHaveBeenCalledWith( - expect.stringContaining('Invalid value "invalid" supplied to "dry_run"') + expect.stringContaining( + "dry_run: Invalid enum value. Expected 'true' | 'false', received 'invalid', dry_run: Expected boolean, received string" + ) ); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_actions/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_actions/route.ts index 14022e9e44af27..8af5eeaa1a0212 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_actions/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_actions/route.ts @@ -20,22 +20,24 @@ import { MAX_RULES_TO_UPDATE_IN_PARALLEL, RULES_TABLE_MAX_PAGE_SIZE, } from '../../../../../../../common/constants'; -import type { PerformBulkActionResponse } from '../../../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import type { + BulkEditActionResponse, + PerformBulkActionResponse, +} from '../../../../../../../common/api/detection_engine/rule_management'; import { - BulkActionType, + BulkActionTypeEnum, PerformBulkActionRequestBody, PerformBulkActionRequestQuery, -} from '../../../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +} from '../../../../../../../common/api/detection_engine/rule_management'; import type { NormalizedRuleError, RuleDetailsInError, - BulkEditActionResponse, BulkEditActionResults, BulkEditActionSummary, } from '../../../../../../../common/api/detection_engine'; import type { SetupPlugins } from '../../../../../../plugin'; import type { SecuritySolutionPluginRouter } from '../../../../../../types'; -import { buildRouteValidation } from '../../../../../../utils/build_validation/route_validation'; +import { buildRouteValidationWithZod } from '../../../../../../utils/build_validation/route_validation'; import { routeLimitedConcurrencyTag } from '../../../../../../utils/route_limited_concurrency_tag'; import type { PromisePoolError, PromisePoolOutcome } from '../../../../../../utils/promise_pool'; import { initPromisePool } from '../../../../../../utils/promise_pool'; @@ -249,8 +251,8 @@ export const performBulkActionRoute = ( version: '2023-10-31', validate: { request: { - body: buildRouteValidation(PerformBulkActionRequestBody), - query: buildRouteValidation(PerformBulkActionRequestQuery), + body: buildRouteValidationWithZod(PerformBulkActionRequestBody), + query: buildRouteValidationWithZod(PerformBulkActionRequestQuery), }, }, }, @@ -272,10 +274,10 @@ export const performBulkActionRoute = ( }); } - const isDryRun = request.query.dry_run === 'true'; + const isDryRun = request.query.dry_run; // dry run is not supported for export, as it doesn't change ES state and has different response format(exported JSON file) - if (isDryRun && body.action === BulkActionType.export) { + if (isDryRun && body.action === BulkActionTypeEnum.export) { return siemResponse.error({ body: `Export action doesn't support dry_run mode`, statusCode: 400, @@ -318,7 +320,7 @@ export const performBulkActionRoute = ( // handling this action before switch statement as bulkEditRules fetch rules within // rulesClient method, hence there is no need to use fetchRulesByQueryOrIds utility - if (body.action === BulkActionType.edit && !isDryRun) { + if (body.action === BulkActionTypeEnum.edit && !isDryRun) { const { rules, errors, skipped } = await bulkEditRules({ rulesClient, filter: query, @@ -348,7 +350,7 @@ export const performBulkActionRoute = ( let deleted: RuleAlertType[] = []; switch (body.action) { - case BulkActionType.enable: + case BulkActionTypeEnum.enable: bulkActionOutcome = await initPromisePool({ concurrency: MAX_RULES_TO_UPDATE_IN_PARALLEL, items: rules, @@ -375,7 +377,7 @@ export const performBulkActionRoute = ( .map(({ result }) => result) .filter((rule): rule is RuleAlertType => rule !== null); break; - case BulkActionType.disable: + case BulkActionTypeEnum.disable: bulkActionOutcome = await initPromisePool({ concurrency: MAX_RULES_TO_UPDATE_IN_PARALLEL, items: rules, @@ -403,7 +405,7 @@ export const performBulkActionRoute = ( .filter((rule): rule is RuleAlertType => rule !== null); break; - case BulkActionType.delete: + case BulkActionTypeEnum.delete: bulkActionOutcome = await initPromisePool({ concurrency: MAX_RULES_TO_UPDATE_IN_PARALLEL, items: rules, @@ -427,7 +429,7 @@ export const performBulkActionRoute = ( .filter((rule): rule is RuleAlertType => rule !== null); break; - case BulkActionType.duplicate: + case BulkActionTypeEnum.duplicate: bulkActionOutcome = await initPromisePool({ concurrency: MAX_RULES_TO_UPDATE_IN_PARALLEL, items: rules, @@ -486,7 +488,7 @@ export const performBulkActionRoute = ( .filter((rule): rule is RuleAlertType => rule !== null); break; - case BulkActionType.export: + case BulkActionTypeEnum.export: const exported = await getExportByObjectIds( rulesClient, exceptionsClient, @@ -510,7 +512,7 @@ export const performBulkActionRoute = ( // will be processed only when isDryRun === true // during dry run only validation is getting performed and rule is not saved in ES - case BulkActionType.edit: + case BulkActionTypeEnum.edit: bulkActionOutcome = await initPromisePool({ concurrency: MAX_RULES_TO_UPDATE_IN_PARALLEL, items: rules, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_patch_rules/route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_patch_rules/route.test.ts index fc3d87d32b4325..ca3cde890b738d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_patch_rules/route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_patch_rules/route.test.ts @@ -191,7 +191,9 @@ describe('Bulk patch rules route', () => { }); const result = server.validate(request); - expect(result.badRequest).toHaveBeenCalledWith('0: Invalid input'); + expect(result.badRequest).toHaveBeenCalledWith( + '0.type: Invalid literal value, expected "eql", 0.language: Invalid literal value, expected "eql", 0.type: Invalid literal value, expected "query", 0.type: Invalid literal value, expected "saved_query", 0.type: Invalid literal value, expected "threshold", and 5 more' + ); }); test('allows rule type of query and custom from and interval', async () => { 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 5fed0b4e3446a1..a1d74b14455081 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 @@ -236,7 +236,9 @@ describe('Create rule route', () => { }, }); const result = await server.validate(request); - expect(result.badRequest).toHaveBeenCalledWith('Invalid input'); + expect(result.badRequest).toHaveBeenCalledWith( + 'type: Invalid literal value, expected "eql", language: Invalid literal value, expected "eql", type: Invalid literal value, expected "saved_query", saved_id: Required, type: Invalid literal value, expected "threshold", and 18 more' + ); }); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/find_rules/route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/find_rules/route.test.ts index 76d63ddcd54b0d..b9a68994a0e580 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/find_rules/route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/find_rules/route.test.ts @@ -76,7 +76,7 @@ describe('Find rules route', () => { expect(result.ok).toHaveBeenCalled(); }); - test('rejects unknown query params', async () => { + test('ignores unknown query params', async () => { const request = requestMock.create({ method: 'get', path: DETECTION_ENGINE_RULES_URL_FIND, @@ -86,7 +86,7 @@ describe('Find rules route', () => { }); const result = server.validate(request); - expect(result.badRequest).toHaveBeenCalledWith('invalid keys "invalid_value"'); + expect(result.ok).toHaveBeenCalled(); }); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/find_rules/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/find_rules/route.ts index 76496d26cb8568..3cbd164586a9df 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/find_rules/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/find_rules/route.ts @@ -18,7 +18,7 @@ import { import type { SecuritySolutionPluginRouter } from '../../../../../../types'; import { findRules } from '../../../logic/search/find_rules'; import { buildSiemResponse } from '../../../../routes/utils'; -import { buildRouteValidation } from '../../../../../../utils/build_validation/route_validation'; +import { buildRouteValidationWithZod } from '../../../../../../utils/build_validation/route_validation'; import { transformFindAlerts } from '../../../utils/utils'; export const findRulesRoute = (router: SecuritySolutionPluginRouter, logger: Logger) => { @@ -35,7 +35,7 @@ export const findRulesRoute = (router: SecuritySolutionPluginRouter, logger: Log version: '2023-10-31', validate: { request: { - query: buildRouteValidation(FindRulesRequestQuery), + query: buildRouteValidationWithZod(FindRulesRequestQuery), }, }, }, @@ -63,11 +63,7 @@ export const findRulesRoute = (router: SecuritySolutionPluginRouter, logger: Log }); const transformed = transformFindAlerts(rules); - if (transformed == null) { - return siemResponse.error({ statusCode: 500, body: 'Internal error transforming' }); - } else { - return response.ok({ body: transformed ?? {} }); - } + return response.ok({ body: transformed ?? {} }); } catch (err) { const error = transformError(err); return siemResponse.error({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.test.ts index 677556f3142396..1255287cf52f5d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.test.ts @@ -199,7 +199,9 @@ describe('Patch rule route', () => { }); const result = server.validate(request); - expect(result.badRequest).toHaveBeenCalledWith('Invalid input'); + expect(result.badRequest).toHaveBeenCalledWith( + 'type: Invalid literal value, expected "eql", language: Invalid literal value, expected "eql", type: Invalid literal value, expected "query", type: Invalid literal value, expected "saved_query", type: Invalid literal value, expected "threshold", and 5 more' + ); }); test('allows rule type of query and custom from and interval', async () => { 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 e580f5cc116623..f95b10fa6154f6 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 { RESPONSE_ACTION_TYPES } from '../../../../../../../common/api/detection_engine/model/rule_response_actions'; +import { ResponseActionTypesEnum } from '../../../../../../../common/api/detection_engine/model/rule_response_actions'; jest.mock('../../../../../machine_learning/authz'); @@ -245,7 +245,7 @@ describe('Update rule route', () => { ...getQueryRuleParams(), responseActions: [ { - actionTypeId: RESPONSE_ACTION_TYPES.ENDPOINT, + actionTypeId: ResponseActionTypesEnum['.endpoint'], params: { command: 'isolate', comment: '', @@ -283,7 +283,9 @@ describe('Update rule route', () => { }, }); const result = await server.validate(request); - expect(result.badRequest).toHaveBeenCalledWith('Invalid input'); + expect(result.badRequest).toHaveBeenCalledWith( + 'type: Invalid literal value, expected "eql", language: Invalid literal value, expected "eql", type: Invalid literal value, expected "saved_query", saved_id: Required, type: Invalid literal value, expected "threshold", and 18 more' + ); }); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/action_to_rules_client_operation.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/action_to_rules_client_operation.test.ts index e598715e8f9ecd..e214b7dc3b3419 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/action_to_rules_client_operation.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/action_to_rules_client_operation.test.ts @@ -5,13 +5,16 @@ * 2.0. */ -import { BulkActionEditType } from '../../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import { BulkActionEditTypeEnum } from '../../../../../../common/api/detection_engine/rule_management'; import { bulkEditActionToRulesClientOperation } from './action_to_rules_client_operation'; describe('bulkEditActionToRulesClientOperation', () => { test('should transform tags bulk edit actions correctly', () => { expect( - bulkEditActionToRulesClientOperation({ type: BulkActionEditType.add_tags, value: ['test'] }) + bulkEditActionToRulesClientOperation({ + type: BulkActionEditTypeEnum.add_tags, + value: ['test'], + }) ).toEqual([ { field: 'tags', @@ -22,7 +25,7 @@ describe('bulkEditActionToRulesClientOperation', () => { }); expect( - bulkEditActionToRulesClientOperation({ type: BulkActionEditType.set_tags, value: ['test'] }) + bulkEditActionToRulesClientOperation({ type: BulkActionEditTypeEnum.set_tags, value: ['test'] }) ).toEqual([ { field: 'tags', @@ -32,7 +35,10 @@ describe('bulkEditActionToRulesClientOperation', () => { ]); expect( - bulkEditActionToRulesClientOperation({ type: BulkActionEditType.delete_tags, value: ['test'] }) + bulkEditActionToRulesClientOperation({ + type: BulkActionEditTypeEnum.delete_tags, + value: ['test'], + }) ).toEqual([ { field: 'tags', @@ -44,7 +50,7 @@ describe('bulkEditActionToRulesClientOperation', () => { test('should transform schedule bulk edit correctly', () => { expect( bulkEditActionToRulesClientOperation({ - type: BulkActionEditType.set_schedule, + type: BulkActionEditTypeEnum.set_schedule, value: { interval: '100m', lookback: '10m', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/action_to_rules_client_operation.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/action_to_rules_client_operation.ts index dfd4ee64c07871..eac694f97944b1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/action_to_rules_client_operation.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/action_to_rules_client_operation.ts @@ -8,8 +8,8 @@ import type { BulkEditOperation } from '@kbn/alerting-plugin/server'; import { transformNormalizedRuleToAlertAction } from '../../../../../../common/detection_engine/transform_actions'; -import type { BulkActionEditForRuleAttributes } from '../../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; -import { BulkActionEditType } from '../../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import type { BulkActionEditForRuleAttributes } from '../../../../../../common/api/detection_engine/rule_management'; +import { BulkActionEditTypeEnum } from '../../../../../../common/api/detection_engine/rule_management'; import { assertUnreachable } from '../../../../../../common/utility_types'; import { transformToActionFrequency } from '../../normalization/rule_actions'; @@ -23,7 +23,7 @@ export const bulkEditActionToRulesClientOperation = ( ): BulkEditOperation[] => { switch (action.type) { // tags actions - case BulkActionEditType.add_tags: + case BulkActionEditTypeEnum.add_tags: return [ { field: 'tags', @@ -32,7 +32,7 @@ export const bulkEditActionToRulesClientOperation = ( }, ]; - case BulkActionEditType.delete_tags: + case BulkActionEditTypeEnum.delete_tags: return [ { field: 'tags', @@ -41,7 +41,7 @@ export const bulkEditActionToRulesClientOperation = ( }, ]; - case BulkActionEditType.set_tags: + case BulkActionEditTypeEnum.set_tags: return [ { field: 'tags', @@ -51,7 +51,7 @@ export const bulkEditActionToRulesClientOperation = ( ]; // rule actions - case BulkActionEditType.add_rule_actions: + case BulkActionEditTypeEnum.add_rule_actions: return [ { field: 'actions', @@ -62,7 +62,7 @@ export const bulkEditActionToRulesClientOperation = ( }, ]; - case BulkActionEditType.set_rule_actions: + case BulkActionEditTypeEnum.set_rule_actions: return [ { field: 'actions', @@ -74,7 +74,7 @@ export const bulkEditActionToRulesClientOperation = ( ]; // schedule actions - case BulkActionEditType.set_schedule: + case BulkActionEditTypeEnum.set_schedule: return [ { field: 'schedule', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/bulk_edit_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/bulk_edit_rules.ts index 76034819b508d5..fd2f1644480c05 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/bulk_edit_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/bulk_edit_rules.ts @@ -7,7 +7,7 @@ import type { RulesClient } from '@kbn/alerting-plugin/server'; -import type { BulkActionEditPayload } from '../../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import type { BulkActionEditPayload } from '../../../../../../common/api/detection_engine/rule_management'; import type { MlAuthz } from '../../../../machine_learning/authz'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/rule_params_modifier.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/rule_params_modifier.test.ts index 03375580995324..93044fc0fed18d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/rule_params_modifier.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/rule_params_modifier.test.ts @@ -6,7 +6,7 @@ */ import { addItemsToArray, deleteItemsFromArray, ruleParamsModifier } from './rule_params_modifier'; -import { BulkActionEditType } from '../../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import { BulkActionEditTypeEnum } from '../../../../../../common/api/detection_engine/rule_management'; import type { RuleAlertType } from '../../../rule_schema'; describe('addItemsToArray', () => { @@ -47,7 +47,7 @@ describe('ruleParamsModifier', () => { test('should increment version if rule is custom (immutable === false)', () => { const { modifiedParams } = ruleParamsModifier(ruleParamsMock, [ { - type: BulkActionEditType.add_index_patterns, + type: BulkActionEditTypeEnum.add_index_patterns, value: ['my-index-*'], }, ]); @@ -57,7 +57,7 @@ describe('ruleParamsModifier', () => { test('should not increment version if rule is prebuilt (immutable === true)', () => { const { modifiedParams } = ruleParamsModifier({ ...ruleParamsMock, immutable: true }, [ { - type: BulkActionEditType.add_index_patterns, + type: BulkActionEditTypeEnum.add_index_patterns, value: ['my-index-*'], }, ]); @@ -130,7 +130,7 @@ describe('ruleParamsModifier', () => { { ...ruleParamsMock, index: existingIndexPatterns } as RuleAlertType['params'], [ { - type: BulkActionEditType.add_index_patterns, + type: BulkActionEditTypeEnum.add_index_patterns, value: indexPatternsToAdd, }, ] @@ -194,7 +194,7 @@ describe('ruleParamsModifier', () => { { ...ruleParamsMock, index: existingIndexPatterns } as RuleAlertType['params'], [ { - type: BulkActionEditType.delete_index_patterns, + type: BulkActionEditTypeEnum.delete_index_patterns, value: indexPatternsToDelete, }, ] @@ -249,7 +249,7 @@ describe('ruleParamsModifier', () => { { ...ruleParamsMock, index: existingIndexPatterns } as RuleAlertType['params'], [ { - type: BulkActionEditType.set_index_patterns, + type: BulkActionEditTypeEnum.set_index_patterns, value: indexPatternsToOverwrite, }, ] @@ -267,7 +267,7 @@ describe('ruleParamsModifier', () => { { dataViewId: testDataViewId } as RuleAlertType['params'], [ { - type: BulkActionEditType.delete_index_patterns, + type: BulkActionEditTypeEnum.delete_index_patterns, value: ['index-2-*'], }, ] @@ -281,7 +281,7 @@ describe('ruleParamsModifier', () => { { dataViewId: 'test-data-view', index: ['test-*'] } as RuleAlertType['params'], [ { - type: BulkActionEditType.set_index_patterns, + type: BulkActionEditTypeEnum.set_index_patterns, value: ['index'], overwrite_data_views: true, }, @@ -296,7 +296,7 @@ describe('ruleParamsModifier', () => { { dataViewId: 'test-data-view', index: ['test-*'] } as RuleAlertType['params'], [ { - type: BulkActionEditType.add_index_patterns, + type: BulkActionEditTypeEnum.add_index_patterns, value: ['index'], overwrite_data_views: true, }, @@ -311,7 +311,7 @@ describe('ruleParamsModifier', () => { { dataViewId: 'test-data-view', index: ['test-*', 'index'] } as RuleAlertType['params'], [ { - type: BulkActionEditType.delete_index_patterns, + type: BulkActionEditTypeEnum.delete_index_patterns, value: ['index'], overwrite_data_views: true, }, @@ -327,7 +327,7 @@ describe('ruleParamsModifier', () => { { dataViewId: 'test-data-view', index: undefined } as RuleAlertType['params'], [ { - type: BulkActionEditType.delete_index_patterns, + type: BulkActionEditTypeEnum.delete_index_patterns, value: ['index'], overwrite_data_views: true, }, @@ -342,7 +342,7 @@ describe('ruleParamsModifier', () => { expect(() => ruleParamsModifier({ type: 'machine_learning' } as RuleAlertType['params'], [ { - type: BulkActionEditType.add_index_patterns, + type: BulkActionEditTypeEnum.add_index_patterns, value: ['my-index-*'], }, ]) @@ -355,7 +355,7 @@ describe('ruleParamsModifier', () => { expect(() => ruleParamsModifier({ type: 'machine_learning' } as RuleAlertType['params'], [ { - type: BulkActionEditType.delete_index_patterns, + type: BulkActionEditTypeEnum.delete_index_patterns, value: ['my-index-*'], }, ]) @@ -368,7 +368,7 @@ describe('ruleParamsModifier', () => { expect(() => ruleParamsModifier({ type: 'machine_learning' } as RuleAlertType['params'], [ { - type: BulkActionEditType.set_index_patterns, + type: BulkActionEditTypeEnum.set_index_patterns, value: ['my-index-*'], }, ]) @@ -381,7 +381,7 @@ describe('ruleParamsModifier', () => { expect(() => ruleParamsModifier({ type: 'esql' } as RuleAlertType['params'], [ { - type: BulkActionEditType.add_index_patterns, + type: BulkActionEditTypeEnum.add_index_patterns, value: ['my-index-*'], }, ]) @@ -392,7 +392,7 @@ describe('ruleParamsModifier', () => { expect(() => ruleParamsModifier({ type: 'esql' } as RuleAlertType['params'], [ { - type: BulkActionEditType.delete_index_patterns, + type: BulkActionEditTypeEnum.delete_index_patterns, value: ['my-index-*'], }, ]) @@ -403,7 +403,7 @@ describe('ruleParamsModifier', () => { expect(() => ruleParamsModifier({ type: 'esql' } as RuleAlertType['params'], [ { - type: BulkActionEditType.set_index_patterns, + type: BulkActionEditTypeEnum.set_index_patterns, value: ['my-index-*'], }, ]) @@ -417,7 +417,7 @@ describe('ruleParamsModifier', () => { test('should set timeline', () => { const { modifiedParams, isParamsUpdateSkipped } = ruleParamsModifier(ruleParamsMock, [ { - type: BulkActionEditType.set_timeline, + type: BulkActionEditTypeEnum.set_timeline, value: { timeline_id: '91832785-286d-4ebe-b884-1a208d111a70', timeline_title: 'Test timeline', @@ -438,7 +438,7 @@ describe('ruleParamsModifier', () => { const FROM_IN_SECONDS = (INTERVAL_IN_MINUTES + LOOKBACK_IN_MINUTES) * 60; const { modifiedParams, isParamsUpdateSkipped } = ruleParamsModifier(ruleParamsMock, [ { - type: BulkActionEditType.set_schedule, + type: BulkActionEditTypeEnum.set_schedule, value: { interval: `${INTERVAL_IN_MINUTES}m`, lookback: `${LOOKBACK_IN_MINUTES}m`, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/rule_params_modifier.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/rule_params_modifier.ts index a519aee713becd..2994d2bf7f1576 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/rule_params_modifier.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/rule_params_modifier.ts @@ -12,8 +12,8 @@ import type { RuleAlertType } from '../../../rule_schema'; import type { BulkActionEditForRuleParams, BulkActionEditPayloadIndexPatterns, -} from '../../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; -import { BulkActionEditType } from '../../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +} from '../../../../../../common/api/detection_engine/rule_management'; +import { BulkActionEditTypeEnum } from '../../../../../../common/api/detection_engine/rule_management'; import { invariant } from '../../../../../../common/utils/invariant'; export const addItemsToArray = (arr: T[], items: T[]): T[] => @@ -52,11 +52,11 @@ const shouldSkipIndexPatternsBulkAction = ( return true; } - if (action.type === BulkActionEditType.add_index_patterns) { + if (action.type === BulkActionEditTypeEnum.add_index_patterns) { return hasIndexPatterns(indexPatterns, action); } - if (action.type === BulkActionEditType.delete_index_patterns) { + if (action.type === BulkActionEditTypeEnum.delete_index_patterns) { return hasNotIndexPattern(indexPatterns, action); } @@ -80,7 +80,7 @@ const applyBulkActionEditToRuleParams = ( switch (action.type) { // index_patterns actions // index pattern is not present in machine learning rule type, so we throw error on it - case BulkActionEditType.add_index_patterns: { + case BulkActionEditTypeEnum.add_index_patterns: { invariant( ruleParams.type !== 'machine_learning', "Index patterns can't be added. Machine learning rule doesn't have index patterns property" @@ -102,7 +102,7 @@ const applyBulkActionEditToRuleParams = ( ruleParams.index = addItemsToArray(ruleParams.index ?? [], action.value); break; } - case BulkActionEditType.delete_index_patterns: { + case BulkActionEditTypeEnum.delete_index_patterns: { invariant( ruleParams.type !== 'machine_learning', "Index patterns can't be deleted. Machine learning rule doesn't have index patterns property" @@ -129,7 +129,7 @@ const applyBulkActionEditToRuleParams = ( } break; } - case BulkActionEditType.set_index_patterns: { + case BulkActionEditTypeEnum.set_index_patterns: { invariant( ruleParams.type !== 'machine_learning', "Index patterns can't be overwritten. Machine learning rule doesn't have index patterns property" @@ -152,7 +152,7 @@ const applyBulkActionEditToRuleParams = ( break; } // timeline actions - case BulkActionEditType.set_timeline: { + case BulkActionEditTypeEnum.set_timeline: { ruleParams = { ...ruleParams, timelineId: action.value.timeline_id || undefined, @@ -162,7 +162,7 @@ const applyBulkActionEditToRuleParams = ( break; } // update look-back period in from and meta.from fields - case BulkActionEditType.set_schedule: { + case BulkActionEditTypeEnum.set_schedule: { const interval = parseInterval(action.value.interval) ?? moment.duration(0); const parsedFrom = parseInterval(action.value.lookback) ?? moment.duration(0); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/split_bulk_edit_actions.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/split_bulk_edit_actions.test.ts index 5bde6c29e6082b..cdaa6ed1afb807 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/split_bulk_edit_actions.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/split_bulk_edit_actions.test.ts @@ -5,20 +5,20 @@ * 2.0. */ -import type { BulkActionEditPayload } from '../../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; -import { BulkActionEditType } from '../../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import type { BulkActionEditPayload } from '../../../../../../common/api/detection_engine/rule_management'; +import { BulkActionEditTypeEnum } from '../../../../../../common/api/detection_engine/rule_management'; import { splitBulkEditActions } from './split_bulk_edit_actions'; const bulkEditActions: BulkActionEditPayload[] = [ - { type: BulkActionEditType.add_index_patterns, value: ['test'] }, - { type: BulkActionEditType.set_index_patterns, value: ['test'] }, - { type: BulkActionEditType.delete_index_patterns, value: ['test'] }, - { type: BulkActionEditType.add_tags, value: ['test'] }, - { type: BulkActionEditType.delete_tags, value: ['test'] }, - { type: BulkActionEditType.set_tags, value: ['test'] }, + { type: BulkActionEditTypeEnum.add_index_patterns, value: ['test'] }, + { type: BulkActionEditTypeEnum.set_index_patterns, value: ['test'] }, + { type: BulkActionEditTypeEnum.delete_index_patterns, value: ['test'] }, + { type: BulkActionEditTypeEnum.add_tags, value: ['test'] }, + { type: BulkActionEditTypeEnum.delete_tags, value: ['test'] }, + { type: BulkActionEditTypeEnum.set_tags, value: ['test'] }, { - type: BulkActionEditType.set_timeline, + type: BulkActionEditTypeEnum.set_timeline, value: { timeline_id: 'a-1', timeline_title: 'Test title' }, }, ]; @@ -28,16 +28,16 @@ describe('splitBulkEditActions', () => { const { attributesActions, paramsActions } = splitBulkEditActions(bulkEditActions); expect(attributesActions).toEqual([ - { type: BulkActionEditType.add_tags, value: ['test'] }, - { type: BulkActionEditType.delete_tags, value: ['test'] }, - { type: BulkActionEditType.set_tags, value: ['test'] }, + { type: BulkActionEditTypeEnum.add_tags, value: ['test'] }, + { type: BulkActionEditTypeEnum.delete_tags, value: ['test'] }, + { type: BulkActionEditTypeEnum.set_tags, value: ['test'] }, ]); expect(paramsActions).toEqual([ - { type: BulkActionEditType.add_index_patterns, value: ['test'] }, - { type: BulkActionEditType.set_index_patterns, value: ['test'] }, - { type: BulkActionEditType.delete_index_patterns, value: ['test'] }, + { type: BulkActionEditTypeEnum.add_index_patterns, value: ['test'] }, + { type: BulkActionEditTypeEnum.set_index_patterns, value: ['test'] }, + { type: BulkActionEditTypeEnum.delete_index_patterns, value: ['test'] }, { - type: BulkActionEditType.set_timeline, + type: BulkActionEditTypeEnum.set_timeline, value: { timeline_id: 'a-1', timeline_title: 'Test title' }, }, ]); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/split_bulk_edit_actions.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/split_bulk_edit_actions.ts index 2896acbea0e85b..da626722155ed1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/split_bulk_edit_actions.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/split_bulk_edit_actions.ts @@ -5,12 +5,12 @@ * 2.0. */ -import { BulkActionEditType } from '../../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import { BulkActionEditTypeEnum } from '../../../../../../common/api/detection_engine/rule_management'; import type { BulkActionEditPayload, BulkActionEditForRuleAttributes, BulkActionEditForRuleParams, -} from '../../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +} from '../../../../../../common/api/detection_engine/rule_management'; /** * Split bulk edit actions in 2 chunks: actions applied to params and @@ -29,15 +29,15 @@ export const splitBulkEditActions = (actions: BulkActionEditPayload[]) => { return actions.reduce((acc, action) => { switch (action.type) { - case BulkActionEditType.set_schedule: + case BulkActionEditTypeEnum.set_schedule: acc.attributesActions.push(action); acc.paramsActions.push(action); break; - case BulkActionEditType.add_tags: - case BulkActionEditType.set_tags: - case BulkActionEditType.delete_tags: - case BulkActionEditType.add_rule_actions: - case BulkActionEditType.set_rule_actions: + case BulkActionEditTypeEnum.add_tags: + case BulkActionEditTypeEnum.set_tags: + case BulkActionEditTypeEnum.delete_tags: + case BulkActionEditTypeEnum.add_rule_actions: + case BulkActionEditTypeEnum.set_rule_actions: acc.attributesActions.push(action); break; default: diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/utils.ts index 214fc16b40a495..d624d9033f2998 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/utils.ts @@ -4,7 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { BulkActionEditType } from '../../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import type { BulkActionEditType } from '../../../../../../common/api/detection_engine/rule_management'; +import { BulkActionEditTypeEnum } from '../../../../../../common/api/detection_engine/rule_management'; /** * helper utility that defines whether bulk edit action is related to index patterns, i.e. one of: @@ -14,7 +15,7 @@ import { BulkActionEditType } from '../../../../../../common/api/detection_engin */ export const isIndexPatternsBulkEditAction = (editAction: BulkActionEditType) => [ - BulkActionEditType.add_index_patterns, - BulkActionEditType.delete_index_patterns, - BulkActionEditType.set_index_patterns, + BulkActionEditTypeEnum.add_index_patterns, + BulkActionEditTypeEnum.delete_index_patterns, + BulkActionEditTypeEnum.set_index_patterns, ].includes(editAction); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/validations.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/validations.ts index fc8d13c27c567f..4a1aef9ed28d75 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/validations.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/validations.ts @@ -10,8 +10,8 @@ import { invariant } from '../../../../../../common/utils/invariant'; import { isMlRule } from '../../../../../../common/machine_learning/helpers'; import { isEsqlRule } from '../../../../../../common/detection_engine/utils'; import { BulkActionsDryRunErrCode } from '../../../../../../common/constants'; -import type { BulkActionEditPayload } from '../../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; -import { BulkActionEditType } from '../../../../../../common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route'; +import type { BulkActionEditPayload } from '../../../../../../common/api/detection_engine/rule_management'; +import { BulkActionEditTypeEnum } from '../../../../../../common/api/detection_engine/rule_management'; import type { RuleAlertType } from '../../../rule_schema'; import { isIndexPatternsBulkEditAction } from './utils'; import { throwDryRunError } from './dry_run'; @@ -100,7 +100,9 @@ export const validateBulkEditRule = async ({ */ const istEditApplicableToImmutableRule = (edit: BulkActionEditPayload[]): boolean => { return edit.every(({ type }) => - [BulkActionEditType.set_rule_actions, BulkActionEditType.add_rule_actions].includes(type) + [BulkActionEditTypeEnum.set_rule_actions, BulkActionEditTypeEnum.add_rule_actions].includes( + type + ) ); }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/search/find_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/search/find_rules.ts index 8fb5f348ae2248..892610df03beaf 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/search/find_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/search/find_rules.ts @@ -5,42 +5,29 @@ * 2.0. */ -import * as t from 'io-ts'; - import type { FindResult, RulesClient } from '@kbn/alerting-plugin/server'; -import { NonEmptyString, UUID } from '@kbn/securitysolution-io-ts-types'; -import type { FindRulesSortFieldOrUndefined } from '../../../../../../common/api/detection_engine/rule_management'; +import type { FindRulesSortField } from '../../../../../../common/api/detection_engine/rule_management'; -import type { - FieldsOrUndefined, - PageOrUndefined, - PerPageOrUndefined, - QueryFilterOrUndefined, - SortOrderOrUndefined, -} from '../../../../../../common/api/detection_engine'; +import type { Page, PerPage, SortOrder } from '../../../../../../common/api/detection_engine'; import type { RuleParams } from '../../../rule_schema'; import { enrichFilterWithRuleTypeMapping } from './enrich_filter_with_rule_type_mappings'; import { transformSortField } from './transform_sort_field'; -type HasReferences = t.TypeOf; -const HasReferences = t.type({ - type: NonEmptyString, - id: UUID, -}); - -type HasReferencesOrUndefined = t.TypeOf; -const HasReferencesOrUndefined = t.union([HasReferences, t.undefined]); +interface HasReferences { + type: string; + id: string; +} export interface FindRuleOptions { rulesClient: RulesClient; - filter: QueryFilterOrUndefined; - fields: FieldsOrUndefined; - sortField: FindRulesSortFieldOrUndefined; - sortOrder: SortOrderOrUndefined; - page: PageOrUndefined; - perPage: PerPageOrUndefined; - hasReference?: HasReferencesOrUndefined; + filter: string | undefined; + fields: string[] | undefined; + sortField: FindRulesSortField | undefined; + sortOrder: SortOrder | undefined; + page: Page | undefined; + perPage: PerPage | undefined; + hasReference?: HasReferences | undefined; } export const findRules = ({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/search/transform_sort_field.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/search/transform_sort_field.ts index 53573879d07df4..b55e51882345a2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/search/transform_sort_field.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/search/transform_sort_field.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { FindRulesSortFieldOrUndefined } from '../../../../../../common/api/detection_engine/rule_management'; +import type { FindRulesSortField } from '../../../../../../common/api/detection_engine/rule_management'; import { assertUnreachable } from '../../../../../../common/utility_types'; /** @@ -37,7 +37,7 @@ import { assertUnreachable } from '../../../../../../common/utility_types'; * @param sortField Sort field parameter from the request * @returns Sort field matching the Alerting framework schema */ -export function transformSortField(sortField: FindRulesSortFieldOrUndefined): string | undefined { +export function transformSortField(sortField?: FindRulesSortField): string | undefined { if (!sortField) { return undefined; } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.test.ts index 07b9c9d0cbcd83..a513e8468d5773 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.test.ts @@ -113,7 +113,8 @@ describe('validate', () => { const validatedOrError = transformValidateBulkError('rule-1', ruleAlert); const expected: BulkError = { error: { - message: 'Invalid input', + message: + 'name: Required, type: Invalid literal value, expected "eql", language: Invalid literal value, expected "eql", name: Required, name: Required, and 22 more', status_code: 500, }, rule_id: 'rule-1', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/api/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/api/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.test.ts index c01f09f1b05348..cf6054c689cdd8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/api/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/api/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.test.ts @@ -9,8 +9,8 @@ import { serverMock, requestContextMock, requestMock } from '../../../../routes/ import { GET_RULE_EXECUTION_EVENTS_URL, - LogLevel, - RuleExecutionEventType, + LogLevelEnum, + RuleExecutionEventTypeEnum, } from '../../../../../../../common/api/detection_engine/rule_monitoring'; import { getRuleExecutionEventsResponseMock } from '../../../../../../../common/api/detection_engine/rule_monitoring/mocks'; import type { GetExecutionEventsArgs } from '../../../logic/rule_execution_log'; @@ -35,8 +35,8 @@ describe('getRuleExecutionEventsRoute', () => { ruleId: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', }, query: { - event_types: `${RuleExecutionEventType['status-change']}`, - log_levels: `${LogLevel.debug},${LogLevel.info}`, + event_types: `${RuleExecutionEventTypeEnum['status-change']}`, + log_levels: `${LogLevelEnum.debug},${LogLevelEnum.info}`, page: 3, }, }); @@ -44,8 +44,8 @@ describe('getRuleExecutionEventsRoute', () => { it('passes request arguments to rule execution log', async () => { const expectedArgs: GetExecutionEventsArgs = { ruleId: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', - eventTypes: [RuleExecutionEventType['status-change']], - logLevels: [LogLevel.debug, LogLevel.info], + eventTypes: [RuleExecutionEventTypeEnum['status-change']], + logLevels: [LogLevelEnum.debug, LogLevelEnum.info], sortOrder: 'desc', page: 3, perPage: 20, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/api/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/api/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.ts index 1049cbb5c89e1a..4a01a6550cabcd 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/api/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/api/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.ts @@ -7,7 +7,7 @@ import { transformError } from '@kbn/securitysolution-es-utils'; import type { IKibanaResponse } from '@kbn/core/server'; -import { buildRouteValidation } from '../../../../../../utils/build_validation/route_validation'; +import { buildRouteValidationWithZod } from '../../../../../../utils/build_validation/route_validation'; import { buildSiemResponse } from '../../../../routes/utils'; import type { SecuritySolutionPluginRouter } from '../../../../../../types'; @@ -36,8 +36,8 @@ export const getRuleExecutionEventsRoute = (router: SecuritySolutionPluginRouter version: '1', validate: { request: { - params: buildRouteValidation(GetRuleExecutionEventsRequestParams), - query: buildRouteValidation(GetRuleExecutionEventsRequestQuery), + params: buildRouteValidationWithZod(GetRuleExecutionEventsRequestParams), + query: buildRouteValidationWithZod(GetRuleExecutionEventsRequestQuery), }, }, }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/detection_engine_health/event_log/aggregations/rule_execution_stats.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/detection_engine_health/event_log/aggregations/rule_execution_stats.ts index 41513554195864..5b667770ffa5de 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/detection_engine_health/event_log/aggregations/rule_execution_stats.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/detection_engine_health/event_log/aggregations/rule_execution_stats.ts @@ -5,31 +5,32 @@ * 2.0. */ -import { mapValues } from 'lodash'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { mapValues } from 'lodash'; import type { AggregatedMetric, + HealthOverviewStats, + LogLevel, NumberOfDetectedGaps, NumberOfExecutions, NumberOfLoggedMessages, - HealthOverviewStats, - TopMessages, RuleExecutionStatus, + TopMessages, } from '../../../../../../../../common/api/detection_engine/rule_monitoring'; import { - RuleExecutionEventType, + LogLevelEnum, + RuleExecutionEventTypeEnum, RuleExecutionStatusEnum, - LogLevel, } from '../../../../../../../../common/api/detection_engine/rule_monitoring'; -import { DEFAULT_PERCENTILES } from '../../../utils/es_aggregations'; -import type { RawData } from '../../../utils/normalization'; -import * as f from '../../../event_log/event_log_fields'; import { ALERTING_PROVIDER, RULE_EXECUTION_LOG_PROVIDER, } from '../../../event_log/event_log_constants'; +import * as f from '../../../event_log/event_log_fields'; +import { DEFAULT_PERCENTILES } from '../../../utils/es_aggregations'; +import type { RawData } from '../../../utils/normalization'; export type RuleExecutionStatsAggregationLevel = 'whole-interval' | 'histogram'; @@ -74,7 +75,7 @@ export const getRuleExecutionStatsAggregation = ( bool: { filter: [ { term: { [f.EVENT_PROVIDER]: RULE_EXECUTION_LOG_PROVIDER } }, - { term: { [f.EVENT_ACTION]: RuleExecutionEventType['status-change'] } }, + { term: { [f.EVENT_ACTION]: RuleExecutionEventTypeEnum['status-change'] } }, ], must_not: [ { @@ -101,7 +102,7 @@ export const getRuleExecutionStatsAggregation = ( bool: { filter: [ { term: { [f.EVENT_PROVIDER]: RULE_EXECUTION_LOG_PROVIDER } }, - { term: { [f.EVENT_ACTION]: RuleExecutionEventType['execution-metrics'] } }, + { term: { [f.EVENT_ACTION]: RuleExecutionEventTypeEnum['execution-metrics'] } }, ], }, }, @@ -144,8 +145,8 @@ export const getRuleExecutionStatsAggregation = ( { terms: { [f.EVENT_ACTION]: [ - RuleExecutionEventType['status-change'], - RuleExecutionEventType.message, + RuleExecutionEventTypeEnum['status-change'], + RuleExecutionEventTypeEnum.message, ], }, }, @@ -162,7 +163,7 @@ export const getRuleExecutionStatsAggregation = ( ? { errors: { filter: { - term: { [f.LOG_LEVEL]: LogLevel.error }, + term: { [f.LOG_LEVEL]: LogLevelEnum.error }, }, aggs: { topErrors: { @@ -176,7 +177,7 @@ export const getRuleExecutionStatsAggregation = ( }, warnings: { filter: { - term: { [f.LOG_LEVEL]: LogLevel.warn }, + term: { [f.LOG_LEVEL]: LogLevelEnum.warn }, }, aggs: { topWarnings: { @@ -263,11 +264,11 @@ const normalizeNumberOfLoggedMessages = ( return { total: Number(messageContainingEvents.doc_count || 0), by_level: { - error: getMessageCount(LogLevel.error), - warn: getMessageCount(LogLevel.warn), - info: getMessageCount(LogLevel.info), - debug: getMessageCount(LogLevel.debug), - trace: getMessageCount(LogLevel.trace), + error: getMessageCount(LogLevelEnum.error), + warn: getMessageCount(LogLevelEnum.warn), + info: getMessageCount(LogLevelEnum.info), + debug: getMessageCount(LogLevelEnum.debug), + trace: getMessageCount(LogLevelEnum.trace), }, }; }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/event_log/register_event_log_provider.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/event_log/register_event_log_provider.ts index 61a321c4272053..6c1accab273ad7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/event_log/register_event_log_provider.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/event_log/register_event_log_provider.ts @@ -12,6 +12,6 @@ import { RULE_EXECUTION_LOG_PROVIDER } from './event_log_constants'; export const registerEventLogProvider = (eventLogService: IEventLogService) => { eventLogService.registerProviderActions( RULE_EXECUTION_LOG_PROVIDER, - Object.keys(RuleExecutionEventType) + RuleExecutionEventType.options ); }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/rule_execution_log/client_for_executors/client.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/rule_execution_log/client_for_executors/client.ts index 101884b284ebcc..8e9a2970f5dbfb 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/rule_execution_log/client_for_executors/client.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/rule_execution_log/client_for_executors/client.ts @@ -16,9 +16,9 @@ import type { import type { RuleExecutionSettings, RuleExecutionStatus, + LogLevel, } from '../../../../../../../common/api/detection_engine/rule_monitoring'; import { - LogLevel, logLevelFromExecutionStatus, LogLevelSetting, logLevelToNumber, @@ -38,6 +38,7 @@ import type { StatusChangeArgs, } from './client_interface'; import type { RuleExecutionMetrics } from '../../../../../../../common/api/detection_engine/rule_monitoring/model'; +import { LogLevelEnum } from '../../../../../../../common/api/detection_engine/rule_monitoring/model'; export const createRuleExecutionLogClientForExecutors = ( settings: RuleExecutionSettings, @@ -59,23 +60,23 @@ export const createRuleExecutionLogClientForExecutors = ( }, trace(...messages: string[]): void { - writeMessage(messages, LogLevel.trace); + writeMessage(messages, LogLevelEnum.trace); }, debug(...messages: string[]): void { - writeMessage(messages, LogLevel.debug); + writeMessage(messages, LogLevelEnum.debug); }, info(...messages: string[]): void { - writeMessage(messages, LogLevel.info); + writeMessage(messages, LogLevelEnum.info); }, warn(...messages: string[]): void { - writeMessage(messages, LogLevel.warn); + writeMessage(messages, LogLevelEnum.warn); }, error(...messages: string[]): void { - writeMessage(messages, LogLevel.error); + writeMessage(messages, LogLevelEnum.error); }, async logStatusChange(args: StatusChangeArgs): Promise { @@ -107,19 +108,19 @@ export const createRuleExecutionLogClientForExecutors = ( const writeMessageToConsole = (message: string, logLevel: LogLevel, logMeta: ExtMeta): void => { switch (logLevel) { - case LogLevel.trace: + case LogLevelEnum.trace: logger.trace(`${message} ${baseLogSuffix}`, logMeta); break; - case LogLevel.debug: + case LogLevelEnum.debug: logger.debug(`${message} ${baseLogSuffix}`, logMeta); break; - case LogLevel.info: + case LogLevelEnum.info: logger.info(`${message} ${baseLogSuffix}`, logMeta); break; - case LogLevel.warn: + case LogLevelEnum.warn: logger.warn(`${message} ${baseLogSuffix}`, logMeta); break; - case LogLevel.error: + case LogLevelEnum.error: logger.error(`${message} ${baseLogSuffix}`, logMeta); break; default: @@ -152,7 +153,7 @@ export const createRuleExecutionLogClientForExecutors = ( const writeExceptionToConsole = (e: unknown, message: string, logMeta: ExtMeta): void => { const logReason = e instanceof Error ? e.stack ?? e.message : String(e); - writeMessageToConsole(`${message}. Reason: ${logReason}`, LogLevel.error, logMeta); + writeMessageToConsole(`${message}. Reason: ${logReason}`, LogLevelEnum.error, logMeta); }; const writeStatusChangeToConsole = (args: NormalizedStatusChangeArgs, logMeta: ExtMeta): void => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/rule_execution_log/event_log/event_log_reader.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/rule_execution_log/event_log/event_log_reader.ts index fae8b6cfe9f5c2..669f3d7e5ee043 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/rule_execution_log/event_log/event_log_reader.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/rule_execution_log/event_log/event_log_reader.ts @@ -9,7 +9,6 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { IEventLogClient, IValidatedEvent } from '@kbn/event-log-plugin/server'; import { MAX_EXECUTION_EVENTS_DISPLAYED } from '@kbn/securitysolution-rules'; -import { prepareKQLStringParam } from '../../../../../../../common/utils/kql'; import type { GetRuleExecutionEventsResponse, GetRuleExecutionResultsResponse, @@ -17,10 +16,11 @@ import type { } from '../../../../../../../common/api/detection_engine/rule_monitoring'; import { LogLevel, - logLevelFromString, + LogLevelEnum, RuleExecutionEventType, - ruleExecutionEventTypeFromString, + RuleExecutionEventTypeEnum, } from '../../../../../../../common/api/detection_engine/rule_monitoring'; +import { prepareKQLStringParam } from '../../../../../../../common/utils/kql'; import { assertUnreachable } from '../../../../../../../common/utility_types'; import { invariant } from '../../../../../../../common/utils/invariant'; @@ -38,11 +38,11 @@ import { } from './aggregations/execution_results'; import type { ExecutionUuidAggResult } from './aggregations/execution_results/types'; -import * as f from '../../event_log/event_log_fields'; import { RULE_EXECUTION_LOG_PROVIDER, RULE_SAVED_OBJECT_TYPE, } from '../../event_log/event_log_constants'; +import * as f from '../../event_log/event_log_fields'; export interface IEventLogReader { getExecutionEvents(args: GetExecutionEventsArgs): Promise; @@ -211,25 +211,27 @@ const normalizeEventSequence = (event: RawEvent): number => { const normalizeLogLevel = (event: RawEvent): LogLevel => { const value = event.log?.level; if (!value) { - return LogLevel.debug; + return LogLevelEnum.debug; } - return logLevelFromString(value) ?? LogLevel.trace; + const result = LogLevel.safeParse(value); + return result.success ? result.data : LogLevelEnum.trace; }; const normalizeEventType = (event: RawEvent): RuleExecutionEventType => { const value = event.event?.action; invariant(value, 'Required "event.action" field is not found'); - return ruleExecutionEventTypeFromString(value) ?? RuleExecutionEventType.message; + const result = RuleExecutionEventType.safeParse(value); + return result.success ? result.data : RuleExecutionEventTypeEnum.message; }; const normalizeEventMessage = (event: RawEvent, type: RuleExecutionEventType): string => { - if (type === RuleExecutionEventType.message) { + if (type === RuleExecutionEventTypeEnum.message) { return event.message || ''; } - if (type === RuleExecutionEventType['status-change']) { + if (type === RuleExecutionEventTypeEnum['status-change']) { invariant( event.kibana?.alert?.rule?.execution?.status, 'Required "kibana.alert.rule.execution.status" field is not found' @@ -241,7 +243,7 @@ const normalizeEventMessage = (event: RawEvent, type: RuleExecutionEventType): s return `Rule changed status to "${status}". ${message}`; } - if (type === RuleExecutionEventType['execution-metrics']) { + if (type === RuleExecutionEventTypeEnum['execution-metrics']) { invariant( event.kibana?.alert?.rule?.execution?.metrics, 'Required "kibana.alert.rule.execution.metrics" field is not found' diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/rule_execution_log/event_log/event_log_writer.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/rule_execution_log/event_log/event_log_writer.ts index 89696e7175a307..b0963546100e25 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/rule_execution_log/event_log/event_log_writer.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/rule_execution_log/event_log/event_log_writer.ts @@ -8,17 +8,20 @@ import { SavedObjectsUtils } from '@kbn/core/server'; import type { IEventLogService } from '@kbn/event-log-plugin/server'; import { SAVED_OBJECT_REL_PRIMARY } from '@kbn/event-log-plugin/server'; +import type { LogLevel } from '../../../../../../../common/api/detection_engine/rule_monitoring'; import { - LogLevel, logLevelFromExecutionStatus, logLevelToNumber, - RuleExecutionEventType, ruleExecutionStatusToNumber, } from '../../../../../../../common/api/detection_engine/rule_monitoring'; import type { RuleExecutionMetrics, RuleExecutionStatus, } from '../../../../../../../common/api/detection_engine/rule_monitoring/model'; +import { + LogLevelEnum, + RuleExecutionEventTypeEnum, +} from '../../../../../../../common/api/detection_engine/rule_monitoring/model'; import { RULE_SAVED_OBJECT_TYPE, RULE_EXECUTION_LOG_PROVIDER, @@ -74,7 +77,7 @@ export const createEventLogWriter = (eventLogService: IEventLogService): IEventL }, event: { kind: 'event', - action: RuleExecutionEventType.message, + action: RuleExecutionEventTypeEnum.message, sequence: sequence++, severity: logLevelToNumber(args.logLevel), }, @@ -116,7 +119,7 @@ export const createEventLogWriter = (eventLogService: IEventLogService): IEventL }, event: { kind: 'event', - action: RuleExecutionEventType['status-change'], + action: RuleExecutionEventTypeEnum['status-change'], sequence: sequence++, severity: logLevelToNumber(logLevel), }, @@ -148,7 +151,7 @@ export const createEventLogWriter = (eventLogService: IEventLogService): IEventL }, logExecutionMetrics: (args: ExecutionMetricsArgs): void => { - const logLevel = LogLevel.debug; + const logLevel = LogLevelEnum.debug; eventLogger.logEvent({ '@timestamp': nowISO(), rule: { @@ -159,7 +162,7 @@ export const createEventLogWriter = (eventLogService: IEventLogService): IEventL }, event: { kind: 'metric', - action: RuleExecutionEventType['execution-metrics'], + action: RuleExecutionEventTypeEnum['execution-metrics'], sequence: sequence++, severity: logLevelToNumber(logLevel), }, 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 fcec7e98c06c2a..672434bfc94d09 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 @@ -7,7 +7,7 @@ import { getScheduleNotificationResponseActionsService } from './schedule_notification_response_actions'; import type { RuleResponseAction } from '../../../../common/api/detection_engine/model/rule_response_actions'; -import { RESPONSE_ACTION_TYPES } from '../../../../common/api/detection_engine/model/rule_response_actions'; +import { ResponseActionTypesEnum } from '../../../../common/api/detection_engine/model/rule_response_actions'; describe('ScheduleNotificationResponseActions', () => { const signalOne = { agent: { id: 'agent-id-1' }, _id: 'alert-id-1', user: { id: 'S-1-5-20' } }; @@ -68,7 +68,7 @@ describe('ScheduleNotificationResponseActions', () => { it('should handle osquery response actions with query', async () => { const responseActions: RuleResponseAction[] = [ { - actionTypeId: RESPONSE_ACTION_TYPES.OSQUERY, + actionTypeId: ResponseActionTypesEnum['.osquery'], params: { ...defaultQueryParams, query: simpleQuery, @@ -86,7 +86,7 @@ describe('ScheduleNotificationResponseActions', () => { it('should handle osquery response actions with packs', async () => { const responseActions: RuleResponseAction[] = [ { - actionTypeId: RESPONSE_ACTION_TYPES.OSQUERY, + actionTypeId: ResponseActionTypesEnum['.osquery'], params: { ...defaultPackParams, queries: [ 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 25efe01d15f052..a02fafe69d8f68 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 @@ -8,7 +8,7 @@ import { each } from 'lodash'; import type { EndpointAppContextService } from '../../../endpoint/endpoint_app_context_services'; import type { SetupPlugins } from '../../../plugin_contract'; -import { RESPONSE_ACTION_TYPES } from '../../../../common/api/detection_engine/model/rule_response_actions'; +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'; @@ -29,14 +29,14 @@ export const getScheduleNotificationResponseActionsService = each(responseActions, (responseAction) => { if ( - responseAction.actionTypeId === RESPONSE_ACTION_TYPES.OSQUERY && + responseAction.actionTypeId === ResponseActionTypesEnum['.osquery'] && osqueryCreateActionService ) { osqueryResponseAction(responseAction, osqueryCreateActionService, { alerts, }); } - if (responseAction.actionTypeId === RESPONSE_ACTION_TYPES.ENDPOINT) { + if (responseAction.actionTypeId === ResponseActionTypesEnum['.endpoint']) { endpointResponseAction(responseAction, endpointAppContextService, { alerts, }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts index ec1db8812966ed..9620998722600e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts @@ -507,7 +507,9 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper = } else { await ruleExecutionLogger.logStatusChange({ newStatus: RuleExecutionStatusEnum.failed, - message: `Bulk Indexing of alerts failed: ${truncateList(result.errors).join()}`, + message: `An error occurred during rule execution: message: "${truncateList( + result.errors + ).join()}"`, metrics: { searchDurations: result.searchAfterTimes, indexingDurations: result.bulkCreateTimes, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/README.md b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/README.md index 70c00d6203c8bb..863a2370f74239 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/README.md +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/README.md @@ -12,7 +12,7 @@ Each page is evaluated in 3 phases. Phase 1: Collect "recent" terms - terms that have appeared in the last rule interval, without regard to whether or not they have appeared in historical data. This is done using a composite aggregation to ensure we can iterate over every term. Phase 2: Check if the page of terms contains any new terms. This uses a regular terms agg with the include parameter - every term is added to the array of include values, so the terms agg is limited to only aggregating on the terms of interest from phase 1. This avoids issues with the terms agg providing approximate results due to getting different terms from different shards. -For multiple new terms fields(['source.host', 'source.ip']), in terms aggregation uses a runtime field. Which is created by joining values from new terms fields into one single keyword value. Fields values encoded in base64 and joined with configured a delimiter symbol, which is not part of base64 symbols(a–Z, 0–9, +, /, =) to avoid a situation when delimiter can be part of field value. Include parameter consists of encoded in base64 results from Phase 1. +For multiple new terms fields(['source.host', 'source.ip']), in composite aggregation uses pagination through phase 1 aggregation results. It is done, by splitting page results(10,000 buckets) into chunks(500 size of a chunk). Each chunk then gets converted into a DSL query as a filter and applied in a single request. Phase 3: Any new terms from phase 2 are processed and the first document to contain that term is retrieved. The document becomes the basis of the generated alert. This is done with an aggregation query that is very similar to the agg used in phase 2, except it also includes a top_hits agg. top_hits is moved to a separate, later phase for efficiency - top_hits is slow and most terms will not be new in phase 2. This means we only execute the top_hits agg on the terms that are actually new which is faster. @@ -27,7 +27,3 @@ The new terms rule type reuses the singleSearchAfter function which implements t ## Limitations and future enhancements - Value list exceptions are not supported at the moment. Commit ead04ce removes an experimental method I tried for evaluating value list exceptions. -- Runtime field supports only 100 emitted values. So for large arrays or combination of values greater than 100, results may not be exhaustive. This applies only to new terms with multiple fields. - Following edge cases possible: - - false negatives (alert is not generated) if too many fields were emitted and actual new values are not getting evaluated if it happened in document in rule run window. - - false positives (wrong alert generated) if too many fields were emitted in historical document and some old terms are not getting evaluated against values in new documents. diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/build_new_terms_aggregation.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/build_new_terms_aggregation.ts index 93ba7064c6ce33..d628d8dbf2d903 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/build_new_terms_aggregation.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/build_new_terms_aggregation.ts @@ -8,6 +8,8 @@ import type { Moment } from 'moment'; import type { ESSearchResponse } from '@kbn/es-types'; import type { SignalSource } from '../types'; +import type { GenericBulkCreateResponse } from '../factories/bulk_create_factory'; +import type { NewTermsFieldsLatest } from '../../../../../common/api/detection_engine/model/alerts'; export type RecentTermsAggResult = ESSearchResponse< SignalSource, @@ -19,23 +21,41 @@ export type NewTermsAggResult = ESSearchResponse< { body: { aggregations: ReturnType } } >; +export type CompositeDocFetchAggResult = ESSearchResponse< + SignalSource, + { body: { aggregations: ReturnType } } +>; + +export type CompositeNewTermsAggResult = ESSearchResponse< + SignalSource, + { body: { aggregations: ReturnType } } +>; + export type DocFetchAggResult = ESSearchResponse< SignalSource, { body: { aggregations: ReturnType } } >; +export type CreateAlertsHook = ( + aggResult: CompositeDocFetchAggResult | DocFetchAggResult +) => Promise>; + const PAGE_SIZE = 10000; /** * Creates an aggregation that pages through all terms. Used to find the terms that have appeared recently, * without regard to whether or not they're actually new. + * @param param.pageSize - defines size of composite aggregation results. default value is 10,000, arguments values used ofr cases when composite aggregations calls split in batches + * refer to multiTermsCompositeNonRetryable method to more details */ export const buildRecentTermsAgg = ({ fields, after, + pageSize, }: { fields: string[]; after: Record | undefined; + pageSize?: number; }) => { const sources = fields.map((field) => ({ [field]: { @@ -49,7 +69,7 @@ export const buildRecentTermsAgg = ({ new_terms: { composite: { sources, - size: PAGE_SIZE, + size: pageSize ?? PAGE_SIZE, after, }, }, @@ -138,3 +158,99 @@ export const buildDocFetchAgg = ({ }, }; }; + +/** + * Creates an aggregation that returns a bucket for each term + */ +export const buildCompositeNewTermsAgg = ({ + newValueWindowStart, + timestampField, + fields, + after, + pageSize, +}: { + newValueWindowStart: Moment; + timestampField: string; + fields: string[]; + after: Record | undefined; + pageSize?: number; +}) => { + return { + new_terms: { + ...buildRecentTermsAgg({ fields, after, pageSize }).new_terms, + aggs: { + first_seen: { + min: { + field: timestampField, + }, + }, + filtering_agg: { + bucket_selector: { + buckets_path: { + first_seen_value: 'first_seen', + }, + script: { + params: { + start_time: newValueWindowStart.valueOf(), + }, + source: 'params.first_seen_value > params.start_time', + }, + }, + }, + }, + }, + }; +}; + +/** + * Creates an aggregation that fetches the oldest document for each term. + */ +export const buildCompositeDocFetchAgg = ({ + fields, + timestampField, + after, + newValueWindowStart, + pageSize, +}: { + newValueWindowStart: Moment; + fields: string[]; + timestampField: string; + after: Record | undefined; + pageSize?: number; +}) => { + return { + new_terms: { + ...buildRecentTermsAgg({ fields, after, pageSize }).new_terms, + aggs: { + first_seen: { + min: { + field: timestampField, + }, + }, + filtering_agg: { + bucket_selector: { + buckets_path: { + first_seen_value: 'first_seen', + }, + script: { + params: { + start_time: newValueWindowStart.valueOf(), + }, + source: 'params.first_seen_value > params.start_time', + }, + }, + }, + docs: { + top_hits: { + size: 1, + sort: [ + { + [timestampField]: 'asc' as const, + }, + ], + }, + }, + }, + }, + }; +}; 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 88212b42fe7137..57a1ecfe38109e 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 @@ -5,6 +5,8 @@ * 2.0. */ +import { isObject } from 'lodash'; + import { NEW_TERMS_RULE_TYPE_ID } from '@kbn/securitysolution-rules'; import { DEFAULT_APP_CATEGORIES } from '@kbn/core-application-common'; import { SERVER_APP_ID } from '../../../../../common/constants'; @@ -16,31 +18,26 @@ import { getFilter } from '../utils/get_filter'; import { wrapNewTermsAlerts } from './wrap_new_terms_alerts'; import type { EventsAndTerms } from './wrap_new_terms_alerts'; import type { - DocFetchAggResult, RecentTermsAggResult, + DocFetchAggResult, NewTermsAggResult, + CreateAlertsHook, } from './build_new_terms_aggregation'; import { - buildDocFetchAgg, buildRecentTermsAgg, buildNewTermsAgg, + buildDocFetchAgg, } from './build_new_terms_aggregation'; import { validateIndexPatterns } from '../utils'; -import { - parseDateString, - validateHistoryWindowStart, - transformBucketsToValues, - getNewTermsRuntimeMappings, - getAggregationField, - decodeMatchedValues, -} from './utils'; +import { parseDateString, validateHistoryWindowStart, transformBucketsToValues } from './utils'; import { addToSearchAfterReturn, createSearchAfterReturnType, - getMaxSignalsWarning, getUnprocessedExceptionsWarnings, + getMaxSignalsWarning, } from '../utils/utils'; import { createEnrichEventsFunction } from '../utils/enrichments'; +import { multiTermsComposite } from './multi_terms_composite'; export const createNewTermsAlertType = ( createOptions: CreateRuleOptions @@ -118,7 +115,7 @@ export const createNewTermsAlertType = ( from: params.from, }); - const esFilter = await getFilter({ + const filterArgs = { filters: params.filters, index: inputIndex, language: params.language, @@ -128,7 +125,8 @@ export const createNewTermsAlertType = ( query: params.query, exceptionFilter, fields: inputIndexFields, - }); + }; + const esFilter = await getFilter(filterArgs); const parsedHistoryWindowSize = parseDateString({ date: params.historyWindowStart, @@ -182,88 +180,87 @@ export const createNewTermsAlertType = ( result.searchAfterTimes.push(searchDuration); result.errors.push(...searchErrors); - afterKey = searchResultWithAggs.aggregations.new_terms.after_key; - // If the aggregation returns no after_key it signals that we've paged through all results // and the current page is empty so we can immediately break. - if (afterKey == null) { + if (searchResultWithAggs.aggregations.new_terms.after_key == null) { break; } const bucketsForField = searchResultWithAggs.aggregations.new_terms.buckets; - const includeValues = transformBucketsToValues(params.newTermsFields, bucketsForField); - const newTermsRuntimeMappings = getNewTermsRuntimeMappings( - params.newTermsFields, - bucketsForField - ); - // PHASE 2: Take the page of results from Phase 1 and determine if each term exists in the history window. - // The aggregation filters out buckets for terms that exist prior to `tuple.from`, so the buckets in the - // response correspond to each new term. - const { - searchResult: pageSearchResult, - searchDuration: pageSearchDuration, - searchErrors: pageSearchErrors, - } = await singleSearchAfter({ - aggregations: buildNewTermsAgg({ - newValueWindowStart: tuple.from, - timestampField: aggregatableTimestampField, - field: getAggregationField(params.newTermsFields), - include: includeValues, - }), - runtimeMappings: { - ...runtimeMappings, - ...newTermsRuntimeMappings, - }, - searchAfterSortIds: undefined, - index: inputIndex, - // For Phase 2, we expand the time range to aggregate over the history window - // in addition to the rule interval - from: parsedHistoryWindowSize.toISOString(), - to: tuple.to.toISOString(), - services, - ruleExecutionLogger, - filter: esFilter, - pageSize: 0, - primaryTimestamp, - secondaryTimestamp, - }); - result.searchAfterTimes.push(pageSearchDuration); - result.errors.push(...pageSearchErrors); - - logger.debug(`Time spent on phase 2 terms agg: ${pageSearchDuration}`); + const createAlertsHook: CreateAlertsHook = async (aggResult) => { + const eventsAndTerms: EventsAndTerms[] = ( + aggResult?.aggregations?.new_terms.buckets ?? [] + ).map((bucket) => { + const newTerms = isObject(bucket.key) ? Object.values(bucket.key) : [bucket.key]; + return { + event: bucket.docs.hits.hits[0], + newTerms, + }; + }); - const pageSearchResultWithAggs = pageSearchResult as NewTermsAggResult; - if (!pageSearchResultWithAggs.aggregations) { - throw new Error('Aggregations were missing on new terms search result'); - } + const wrappedAlerts = wrapNewTermsAlerts({ + eventsAndTerms, + spaceId, + completeRule, + mergeStrategy, + indicesToQuery: inputIndex, + alertTimestampOverride, + ruleExecutionLogger, + publicBaseUrl, + }); - // PHASE 3: For each term that is not in the history window, fetch the oldest document in - // the rule interval for that term. This is the first document to contain the new term, and will - // become the basis of the resulting alert. - // One document could become multiple alerts if the document contains an array with multiple new terms. - if (pageSearchResultWithAggs.aggregations.new_terms.buckets.length > 0) { - const actualNewTerms = pageSearchResultWithAggs.aggregations.new_terms.buckets.map( - (bucket) => bucket.key + const bulkCreateResult = await bulkCreate( + wrappedAlerts, + params.maxSignals - result.createdSignalsCount, + createEnrichEventsFunction({ + services, + logger: ruleExecutionLogger, + }) ); + addToSearchAfterReturn({ current: result, next: bulkCreateResult }); + + return bulkCreateResult; + }; + + // separate route for multiple new terms + // it uses paging through composite aggregation + if (params.newTermsFields.length > 1) { + await multiTermsComposite({ + filterArgs, + buckets: bucketsForField, + params, + aggregatableTimestampField, + parsedHistoryWindowSize, + services, + result, + logger, + runOpts: execOptions.runOpts, + afterKey, + createAlertsHook, + }); + } else { + // PHASE 2: Take the page of results from Phase 1 and determine if each term exists in the history window. + // The aggregation filters out buckets for terms that exist prior to `tuple.from`, so the buckets in the + // response correspond to each new term. + const includeValues = transformBucketsToValues(params.newTermsFields, bucketsForField); const { - searchResult: docFetchSearchResult, - searchDuration: docFetchSearchDuration, - searchErrors: docFetchSearchErrors, + searchResult: pageSearchResult, + searchDuration: pageSearchDuration, + searchErrors: pageSearchErrors, } = await singleSearchAfter({ - aggregations: buildDocFetchAgg({ + aggregations: buildNewTermsAgg({ + newValueWindowStart: tuple.from, timestampField: aggregatableTimestampField, - field: getAggregationField(params.newTermsFields), - include: actualNewTerms, + field: params.newTermsFields[0], + include: includeValues, }), - runtimeMappings: { - ...runtimeMappings, - ...newTermsRuntimeMappings, - }, + runtimeMappings, searchAfterSortIds: undefined, index: inputIndex, - // For phase 3, we go back to aggregating only over the rule interval - excluding the history window - from: tuple.from.toISOString(), + // For Phase 2, we expand the time range to aggregate over the history window + // in addition to the rule interval + from: parsedHistoryWindowSize.toISOString(), to: tuple.to.toISOString(), services, ruleExecutionLogger, @@ -272,51 +269,67 @@ export const createNewTermsAlertType = ( primaryTimestamp, secondaryTimestamp, }); - result.searchAfterTimes.push(docFetchSearchDuration); - result.errors.push(...docFetchSearchErrors); + result.searchAfterTimes.push(pageSearchDuration); + result.errors.push(...pageSearchErrors); - const docFetchResultWithAggs = docFetchSearchResult as DocFetchAggResult; + logger.debug(`Time spent on phase 2 terms agg: ${pageSearchDuration}`); - if (!docFetchResultWithAggs.aggregations) { - throw new Error('Aggregations were missing on document fetch search result'); + const pageSearchResultWithAggs = pageSearchResult as NewTermsAggResult; + if (!pageSearchResultWithAggs.aggregations) { + throw new Error('Aggregations were missing on new terms search result'); } - const eventsAndTerms: EventsAndTerms[] = - docFetchResultWithAggs.aggregations.new_terms.buckets.map((bucket) => { - const newTerms = decodeMatchedValues(params.newTermsFields, bucket.key); - return { - event: bucket.docs.hits.hits[0], - newTerms, - }; + // PHASE 3: For each term that is not in the history window, fetch the oldest document in + // the rule interval for that term. This is the first document to contain the new term, and will + // become the basis of the resulting alert. + // One document could become multiple alerts if the document contains an array with multiple new terms. + if (pageSearchResultWithAggs.aggregations.new_terms.buckets.length > 0) { + const actualNewTerms = pageSearchResultWithAggs.aggregations.new_terms.buckets.map( + (bucket) => bucket.key + ); + + const { + searchResult: docFetchSearchResult, + searchDuration: docFetchSearchDuration, + searchErrors: docFetchSearchErrors, + } = await singleSearchAfter({ + aggregations: buildDocFetchAgg({ + timestampField: aggregatableTimestampField, + field: params.newTermsFields[0], + include: actualNewTerms, + }), + runtimeMappings, + searchAfterSortIds: undefined, + index: inputIndex, + // For phase 3, we go back to aggregating only over the rule interval - excluding the history window + from: tuple.from.toISOString(), + to: tuple.to.toISOString(), + services, + ruleExecutionLogger, + filter: esFilter, + pageSize: 0, + primaryTimestamp, + secondaryTimestamp, }); + result.searchAfterTimes.push(docFetchSearchDuration); + result.errors.push(...docFetchSearchErrors); - const wrappedAlerts = wrapNewTermsAlerts({ - eventsAndTerms, - spaceId, - completeRule, - mergeStrategy, - indicesToQuery: inputIndex, - alertTimestampOverride, - ruleExecutionLogger, - publicBaseUrl, - }); + const docFetchResultWithAggs = docFetchSearchResult as DocFetchAggResult; - const bulkCreateResult = await bulkCreate( - wrappedAlerts, - params.maxSignals - result.createdSignalsCount, - createEnrichEventsFunction({ - services, - logger: ruleExecutionLogger, - }) - ); + if (!docFetchResultWithAggs.aggregations) { + throw new Error('Aggregations were missing on document fetch search result'); + } - addToSearchAfterReturn({ current: result, next: bulkCreateResult }); + const bulkCreateResult = await createAlertsHook(docFetchResultWithAggs); - if (bulkCreateResult.alertsWereTruncated) { - result.warningMessages.push(getMaxSignalsWarning()); - break; + if (bulkCreateResult.alertsWereTruncated) { + result.warningMessages.push(getMaxSignalsWarning()); + break; + } } } + + afterKey = searchResultWithAggs.aggregations.new_terms.after_key; } return { ...result, state }; }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/multi_terms_composite.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/multi_terms_composite.ts new file mode 100644 index 00000000000000..dc33626bbd5352 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/multi_terms_composite.ts @@ -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 pRetry from 'p-retry'; +import type { Moment } from 'moment'; +import type { Logger } from '@kbn/logging'; +import type { NewTermsRuleParams } from '../../rule_schema'; +import type { GetFilterArgs } from '../utils/get_filter'; +import { getFilter } from '../utils/get_filter'; +import { singleSearchAfter } from '../utils/single_search_after'; +import { + buildCompositeNewTermsAgg, + buildCompositeDocFetchAgg, +} from './build_new_terms_aggregation'; + +import type { + CompositeDocFetchAggResult, + CompositeNewTermsAggResult, + CreateAlertsHook, +} from './build_new_terms_aggregation'; + +import { getMaxSignalsWarning } from '../utils/utils'; + +import type { RuleServices, SearchAfterAndBulkCreateReturnType, RunOpts } from '../types'; + +/** + * composite aggregation page batch size set to 500 as it shows th best performance(refer https://github.com/elastic/kibana/pull/157413) and + * allows to be scaled down below when max_clause_count error is encountered + */ +const BATCH_SIZE = 500; + +interface MultiTermsCompositeArgsBase { + filterArgs: GetFilterArgs; + buckets: Array<{ + doc_count: number; + key: Record; + }>; + params: NewTermsRuleParams; + aggregatableTimestampField: string; + parsedHistoryWindowSize: Moment; + services: RuleServices; + result: SearchAfterAndBulkCreateReturnType; + logger: Logger; + runOpts: RunOpts; + afterKey: Record | undefined; + createAlertsHook: CreateAlertsHook; +} + +interface MultiTermsCompositeArgs extends MultiTermsCompositeArgsBase { + batchSize: number; +} + +/** + * This helper does phase2/phase3(look README) got multiple new terms + * It takes full page of results from phase 1 (10,000) + * Splits it in chunks (starts from 500) and applies it as a filter in new composite aggregation request + * It pages through though all 10,000 results from phase1 until maxSize alerts found + */ +const multiTermsCompositeNonRetryable = async ({ + filterArgs, + buckets, + params, + aggregatableTimestampField, + parsedHistoryWindowSize, + services, + result, + logger, + runOpts, + afterKey, + createAlertsHook, + batchSize, +}: MultiTermsCompositeArgs) => { + const { + ruleExecutionLogger, + tuple, + inputIndex, + runtimeMappings, + primaryTimestamp, + secondaryTimestamp, + } = runOpts; + + let internalAfterKey = afterKey ?? undefined; + + let i = 0; + + while (i < buckets.length) { + const batch = buckets.slice(i, i + batchSize); + i += batchSize; + const batchFilters = batch.map((b) => { + const must = Object.keys(b.key).map((key) => ({ match: { [key]: b.key[key] } })); + + return { bool: { must } }; + }); + + const esFilterForBatch = await getFilter({ + ...filterArgs, + filters: [ + ...(Array.isArray(filterArgs.filters) ? filterArgs.filters : []), + { bool: { should: batchFilters } }, + ], + }); + + // PHASE 2: Take the page of results from Phase 1 and determine if each term exists in the history window. + // The aggregation filters out buckets for terms that exist prior to `tuple.from`, so the buckets in the + // response correspond to each new term. + const { + searchResult: pageSearchResult, + searchDuration: pageSearchDuration, + searchErrors: pageSearchErrors, + } = await singleSearchAfter({ + aggregations: buildCompositeNewTermsAgg({ + newValueWindowStart: tuple.from, + timestampField: aggregatableTimestampField, + fields: params.newTermsFields, + after: internalAfterKey, + pageSize: batchSize, + }), + runtimeMappings, + searchAfterSortIds: undefined, + index: inputIndex, + // For Phase 2, we expand the time range to aggregate over the history window + // in addition to the rule interval + from: parsedHistoryWindowSize.toISOString(), + to: tuple.to.toISOString(), + services, + ruleExecutionLogger, + filter: esFilterForBatch, + pageSize: 0, + primaryTimestamp, + secondaryTimestamp, + }); + + result.searchAfterTimes.push(pageSearchDuration); + result.errors.push(...pageSearchErrors); + logger.debug(`Time spent on phase 2 terms agg: ${pageSearchDuration}`); + + const pageSearchResultWithAggs = pageSearchResult as CompositeNewTermsAggResult; + if (!pageSearchResultWithAggs.aggregations) { + throw new Error('Aggregations were missing on new terms search result'); + } + + // PHASE 3: For each term that is not in the history window, fetch the oldest document in + // the rule interval for that term. This is the first document to contain the new term, and will + // become the basis of the resulting alert. + // One document could become multiple alerts if the document contains an array with multiple new terms. + if (pageSearchResultWithAggs.aggregations.new_terms.buckets.length > 0) { + const { + searchResult: docFetchSearchResult, + searchDuration: docFetchSearchDuration, + searchErrors: docFetchSearchErrors, + } = await singleSearchAfter({ + aggregations: buildCompositeDocFetchAgg({ + newValueWindowStart: tuple.from, + timestampField: aggregatableTimestampField, + fields: params.newTermsFields, + after: internalAfterKey, + pageSize: batchSize, + }), + runtimeMappings, + searchAfterSortIds: undefined, + index: inputIndex, + from: parsedHistoryWindowSize.toISOString(), + to: tuple.to.toISOString(), + services, + ruleExecutionLogger, + filter: esFilterForBatch, + pageSize: 0, + primaryTimestamp, + secondaryTimestamp, + }); + result.searchAfterTimes.push(docFetchSearchDuration); + result.errors.push(...docFetchSearchErrors); + + const docFetchResultWithAggs = docFetchSearchResult as CompositeDocFetchAggResult; + + if (!docFetchResultWithAggs.aggregations) { + throw new Error('Aggregations were missing on document fetch search result'); + } + + const bulkCreateResult = await createAlertsHook(docFetchResultWithAggs); + + if (bulkCreateResult.alertsWereTruncated) { + result.warningMessages.push(getMaxSignalsWarning()); + return result; + } + } + + internalAfterKey = batch[batch.length - 1]?.key; + } + + return result; +}; + +/** + * If request fails with batch size of BATCH_SIZE + * We will try to reduce it in twice per each request, three times, up until 125 + * Per ES documentation, max_clause_count min value is 1,000 - so with 125 we should be able execute query below max_clause_count value + */ +export const multiTermsComposite = async (args: MultiTermsCompositeArgsBase): Promise => { + let retryBatchSize = BATCH_SIZE; + const ruleExecutionLogger = args.runOpts.ruleExecutionLogger; + await pRetry( + async (retryCount) => { + try { + const res = await multiTermsCompositeNonRetryable({ ...args, batchSize: retryBatchSize }); + return res; + } catch (e) { + // do not retry if error not related to too many clauses + // if user's configured rule somehow has filter itself greater than max_clause_count, we won't get to this place anyway, + // as rule would fail on phase 1 + if ( + ![ + 'query_shard_exception: failed to create query', + 'Query contains too many nested clauses;', + ].some((errMessage) => e.message.includes(errMessage)) + ) { + args.result.errors.push(e.message); + return args.result; + } + + retryBatchSize = retryBatchSize / 2; + ruleExecutionLogger.warn( + `New terms query for multiple fields failed due to too many clauses in query: ${e.message}. Retrying #${retryCount} with ${retryBatchSize} for composite aggregation` + ); + throw e; + } + }, + { + retries: 2, + } + ); +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/utils.test.ts index 9d9993d471e303..8ede65f0adcb51 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/utils.test.ts @@ -5,16 +5,7 @@ * 2.0. */ -import { - parseDateString, - validateHistoryWindowStart, - transformBucketsToValues, - getAggregationField, - decodeMatchedValues, - getNewTermsRuntimeMappings, - createFieldValuesMap, - AGG_FIELD_NAME, -} from './utils'; +import { parseDateString, validateHistoryWindowStart, transformBucketsToValues } from './utils'; describe('new terms utils', () => { describe('parseDateString', () => { @@ -119,257 +110,6 @@ describe('new terms utils', () => { ).toEqual(['host-0']); }); - it('should return correct value for multiple new terms fields', () => { - expect( - transformBucketsToValues( - ['source.host', 'source.ip'], - [ - { - key: { - 'source.host': 'host-0', - 'source.ip': '127.0.0.1', - }, - doc_count: 1, - }, - { - key: { - 'source.host': 'host-1', - 'source.ip': '127.0.0.1', - }, - doc_count: 1, - }, - ] - ) - ).toEqual(['aG9zdC0w_MTI3LjAuMC4x', 'aG9zdC0x_MTI3LjAuMC4x']); - }); - - it('should filter null values for multiple new terms fields', () => { - expect( - transformBucketsToValues( - ['source.host', 'source.ip'], - [ - { - key: { - 'source.host': 'host-0', - 'source.ip': '127.0.0.1', - }, - doc_count: 1, - }, - { - key: { - 'source.host': 'host-1', - 'source.ip': null, - }, - doc_count: 1, - }, - ] - ) - ).toEqual(['aG9zdC0w_MTI3LjAuMC4x']); - }); - }); - - describe('getAggregationField', () => { - it('should return correct value for a single new terms field', () => { - expect(getAggregationField(['source.ip'])).toBe('source.ip'); - }); - it('should return correct value for multiple new terms fields', () => { - expect(getAggregationField(['source.host', 'source.ip'])).toBe(AGG_FIELD_NAME); - }); - }); - - describe('decodeMatchedValues', () => { - it('should return correct value for a single new terms field', () => { - expect(decodeMatchedValues(['source.ip'], '127.0.0.1')).toEqual(['127.0.0.1']); - }); - it('should return correct value for multiple new terms fields', () => { - expect(decodeMatchedValues(['source.host', 'source.ip'], 'aG9zdC0w_MTI3LjAuMC4x')).toEqual([ - 'host-0', - '127.0.0.1', - ]); - }); - }); - - describe('getNewTermsRuntimeMappings', () => { - it('should not return runtime field if new terms fields is empty', () => { - expect(getNewTermsRuntimeMappings([], [])).toBeUndefined(); - }); - it('should not return runtime field if new terms fields has only one field', () => { - expect(getNewTermsRuntimeMappings(['host.name'], [])).toBeUndefined(); - }); - - it('should return runtime field if new terms fields has more than one field', () => { - const runtimeMappings = getNewTermsRuntimeMappings( - ['source.host', 'source.ip'], - [ - { - key: { - 'source.host': 'host-0', - 'source.ip': '127.0.0.1', - }, - doc_count: 1, - }, - { - key: { - 'source.host': 'host-1', - 'source.ip': '127.0.0.1', - }, - doc_count: 1, - }, - ] - ); - - expect(runtimeMappings?.[AGG_FIELD_NAME]).toMatchObject({ - type: 'keyword', - script: { - params: { - fields: ['source.host', 'source.ip'], - values: { - 'source.host': { - 'host-0': true, - 'host-1': true, - }, - 'source.ip': { - '127.0.0.1': true, - }, - }, - }, - source: expect.any(String), - }, - }); - }); - }); -}); - -describe('createFieldValuesMap', () => { - it('should return undefined if new terms fields has only one field', () => { - expect( - createFieldValuesMap( - ['host.name'], - [ - { - key: { - 'source.host': 'host-0', - }, - doc_count: 1, - }, - { - key: { - 'source.host': 'host-1', - }, - doc_count: 3, - }, - ] - ) - ).toBeUndefined(); - }); - - it('should return values map if new terms fields has more than one field', () => { - expect( - createFieldValuesMap( - ['source.host', 'source.ip'], - [ - { - key: { - 'source.host': 'host-0', - 'source.ip': '127.0.0.1', - }, - doc_count: 1, - }, - { - key: { - 'source.host': 'host-1', - 'source.ip': '127.0.0.1', - }, - doc_count: 1, - }, - ] - ) - ).toEqual({ - 'source.host': { - 'host-0': true, - 'host-1': true, - }, - 'source.ip': { - '127.0.0.1': true, - }, - }); - }); - - it('should not put value in map if it is null', () => { - expect( - createFieldValuesMap( - ['source.host', 'source.ip'], - [ - { - key: { - 'source.host': 'host-1', - 'source.ip': null, - }, - doc_count: 1, - }, - ] - ) - ).toEqual({ - 'source.host': { - 'host-1': true, - }, - 'source.ip': {}, - }); - }); - - it('should put value in map if it is a number', () => { - expect( - createFieldValuesMap( - ['source.host', 'source.id'], - [ - { - key: { - 'source.host': 'host-1', - 'source.id': 100, - }, - doc_count: 1, - }, - ] - ) - ).toEqual({ - 'source.host': { - 'host-1': true, - }, - 'source.id': { - '100': true, - }, - }); - }); - - it('should put value in map if it is a boolean', () => { - expect( - createFieldValuesMap( - ['source.host', 'user.enabled'], - [ - { - key: { - 'source.host': 'host-1', - 'user.enabled': true, - }, - doc_count: 1, - }, - { - key: { - 'source.host': 'host-1', - 'user.enabled': false, - }, - doc_count: 1, - }, - ] - ) - ).toEqual({ - 'source.host': { - 'host-1': true, - }, - 'user.enabled': { - true: true, - false: true, - }, - }); + // TODO: write test for multiple fields? }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/utils.ts index de5822d29b1b57..30faf1f64fa963 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/utils.ts @@ -9,9 +9,6 @@ import dateMath from '@elastic/datemath'; import moment from 'moment'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -export const AGG_FIELD_NAME = 'new_terms_values'; -const DELIMITER = '_'; - export const parseDateString = ({ date, forceNow, @@ -54,143 +51,18 @@ export const validateHistoryWindowStart = ({ /** * Takes a list of buckets and creates value from them to be used in 'include' clause of terms aggregation. * For a single new terms field, value equals to bucket name - * For multiple new terms fields and buckets, value equals to concatenated base64 encoded bucket names - * @returns for buckets('host-0', 'test'), resulted value equals to: 'aG9zdC0w_dGVzdA==' + * Otherwise throws error + * @returns */ export const transformBucketsToValues = ( newTermsFields: string[], buckets: estypes.AggregationsCompositeBucket[] ): Array => { - // if new terms include only one field we don't use runtime mappings and don't stich fields buckets together + // if new terms include only one field we don't use runtime mappings and don't stitch fields buckets together if (newTermsFields.length === 1) { return buckets .map((bucket) => Object.values(bucket.key)[0]) .filter((value): value is string | number => value != null); } - - return buckets - .map((bucket) => Object.values(bucket.key)) - .filter((values) => !values.some((value) => value == null)) - .map((values) => - values - .map((value) => - Buffer.from(typeof value !== 'string' ? value.toString() : value).toString('base64') - ) - .join(DELIMITER) - ); -}; - -/** - * transforms arrays of new terms fields and its values in object - * [new_terms_field]: { [value1]: true, [value1]: true } - * It's needed to have constant time complexity of accessing whether value is present in new terms - * It will be passed to Painless script used in runtime field - */ -export const createFieldValuesMap = ( - newTermsFields: string[], - buckets: estypes.AggregationsCompositeBucket[] -) => { - if (newTermsFields.length === 1) { - return undefined; - } - - const valuesMap = newTermsFields.reduce>>( - (acc, field) => ({ ...acc, [field]: {} }), - {} - ); - - buckets - .map((bucket) => bucket.key) - .forEach((bucket) => { - Object.entries(bucket).forEach(([key, value]) => { - if (value == null) { - return; - } - const strValue = typeof value !== 'string' ? value.toString() : value; - valuesMap[key][strValue] = true; - }); - }); - - return valuesMap; -}; - -export const getNewTermsRuntimeMappings = ( - newTermsFields: string[], - buckets: estypes.AggregationsCompositeBucket[] -): undefined | { [AGG_FIELD_NAME]: estypes.MappingRuntimeField } => { - // if new terms include only one field we don't use runtime mappings and don't stich fields buckets together - if (newTermsFields.length <= 1) { - return undefined; - } - - const values = createFieldValuesMap(newTermsFields, buckets); - return { - [AGG_FIELD_NAME]: { - type: 'keyword', - script: { - params: { fields: newTermsFields, values }, - source: ` - def stack = new Stack(); - // ES has limit in 100 values for runtime field, after this query will fail - int emitLimit = 100; - stack.add([0, '']); - - while (stack.length > 0) { - if (emitLimit == 0) { - break; - } - def tuple = stack.pop(); - def index = tuple[0]; - def line = tuple[1]; - if (index === params['fields'].length) { - emit(line); - emitLimit = emitLimit - 1; - } else { - def fieldName = params['fields'][index]; - for (field in doc[fieldName]) { - def fieldStr = String.valueOf(field); - if (!params['values'][fieldName].containsKey(fieldStr)) { - continue; - } - def delimiter = index === 0 ? '' : '${DELIMITER}'; - def nextLine = line + delimiter + fieldStr.encodeBase64(); - - stack.add([index + 1, nextLine]) - } - } - } - `, - }, - }, - }; -}; - -/** - * For a single new terms field, aggregation field equals to new terms field - * For multiple new terms fields, aggregation field equals to defined AGG_FIELD_NAME, which is runtime field - */ -export const getAggregationField = (newTermsFields: string[]): string => { - // if new terms include only one field we don't use runtime mappings and don't stich fields buckets together - if (newTermsFields.length === 1) { - return newTermsFields[0]; - } - - return AGG_FIELD_NAME; -}; - -const decodeBucketKey = (bucketKey: string): string[] => { - return bucketKey - .split(DELIMITER) - .map((encodedValue) => Buffer.from(encodedValue, 'base64').toString()); -}; - -/** - * decodes matched values(bucket keys) from terms aggregation and returns fields as array - * @returns 'aG9zdC0w_dGVzdA==' bucket key will result in ['host-0', 'test'] - */ -export const decodeMatchedValues = (newTermsFields: string[], bucketKey: string | number) => { - // if newTermsFields has length greater than 1, bucketKey can't be number, so casting is safe here - const values = newTermsFields.length === 1 ? [bucketKey] : decodeBucketKey(bucketKey as string); - - return values; + throw Error('Can be used for single new terms field only'); }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/get_filter.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/get_filter.ts index 1d8b09053b4667..7d7492bd17e2b5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/get_filter.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/get_filter.ts @@ -28,7 +28,7 @@ import { withSecuritySpan } from '../../../../utils/with_security_span'; import type { ESBoolQuery } from '../../../../../common/typed_json'; import { getQueryFilter } from './get_query_filter'; -interface GetFilterArgs { +export interface GetFilterArgs { type: Type; filters: unknown | undefined; language: LanguageOrUndefined; diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/calculate_and_persist_risk_scores.mock.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/calculate_and_persist_risk_scores.mock.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/risk_engine/calculate_and_persist_risk_scores.mock.ts rename to x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/calculate_and_persist_risk_scores.mock.ts diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/calculate_and_persist_risk_scores.test.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/calculate_and_persist_risk_scores.test.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/risk_engine/calculate_and_persist_risk_scores.test.ts rename to x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/calculate_and_persist_risk_scores.test.ts diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/calculate_and_persist_risk_scores.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/calculate_and_persist_risk_scores.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/risk_engine/calculate_and_persist_risk_scores.ts rename to x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/calculate_and_persist_risk_scores.ts diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/calculate_risk_scores.mock.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/calculate_risk_scores.mock.ts similarity index 97% rename from x-pack/plugins/security_solution/server/lib/risk_engine/calculate_risk_scores.mock.ts rename to x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/calculate_risk_scores.mock.ts index 183a00eb0fe5c3..c81d1336c162b9 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/calculate_risk_scores.mock.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/calculate_risk_scores.mock.ts @@ -9,7 +9,7 @@ import { ALERT_RISK_SCORE, ALERT_RULE_NAME, } from '@kbn/rule-registry-plugin/common/technical_rule_data_field_names'; -import { RiskCategories } from '../../../common/risk_engine'; +import { RiskCategories } from '../../../../common/risk_engine'; import type { CalculateRiskScoreAggregations, CalculateScoresResponse, diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/calculate_risk_scores.test.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/calculate_risk_scores.test.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/risk_engine/calculate_risk_scores.test.ts rename to x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/calculate_risk_scores.test.ts diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/calculate_risk_scores.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/calculate_risk_scores.ts similarity index 97% rename from x-pack/plugins/security_solution/server/lib/risk_engine/calculate_risk_scores.ts rename to x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/calculate_risk_scores.ts index a5c22b867b72fc..ecacf950202517 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/calculate_risk_scores.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/calculate_risk_scores.ts @@ -22,9 +22,9 @@ import type { IdentifierType, RiskWeights, RiskScore, -} from '../../../common/risk_engine'; -import { RiskCategories } from '../../../common/risk_engine'; -import { withSecuritySpan } from '../../utils/with_security_span'; +} from '../../../../common/risk_engine'; +import { RiskCategories } from '../../../../common/risk_engine'; +import { withSecuritySpan } from '../../../utils/with_security_span'; import { getAfterKeyForIdentifierType, getFieldForIdentifierAgg } from './helpers'; import { buildCategoryCountDeclarations, diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/configurations.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/configurations.ts similarity index 97% rename from x-pack/plugins/security_solution/server/lib/risk_engine/configurations.ts rename to x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/configurations.ts index 6f9a49bb47bbb6..35547187e4ddc0 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/configurations.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/configurations.ts @@ -5,8 +5,8 @@ * 2.0. */ import type { FieldMap } from '@kbn/alerts-as-data-utils'; -import type { IdentifierType } from '../../../common/risk_engine'; -import { RiskScoreEntity, riskScoreBaseIndexName } from '../../../common/risk_engine'; +import type { IdentifierType } from '../../../../common/risk_engine'; +import { RiskScoreEntity, riskScoreBaseIndexName } from '../../../../common/risk_engine'; import type { IIndexPatternString } from './utils/create_datastream'; const commonRiskFields: FieldMap = { diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/get_risk_inputs_index.mock.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/get_risk_inputs_index.mock.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/risk_engine/get_risk_inputs_index.mock.ts rename to x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/get_risk_inputs_index.mock.ts diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/get_risk_inputs_index.test.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/get_risk_inputs_index.test.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/risk_engine/get_risk_inputs_index.test.ts rename to x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/get_risk_inputs_index.test.ts diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/get_risk_inputs_index.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/get_risk_inputs_index.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/risk_engine/get_risk_inputs_index.ts rename to x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/get_risk_inputs_index.ts diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/helpers.test.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/helpers.test.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/risk_engine/helpers.test.ts rename to x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/helpers.test.ts diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/helpers.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/helpers.ts similarity index 97% rename from x-pack/plugins/security_solution/server/lib/risk_engine/helpers.ts rename to x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/helpers.ts index 90a7b0c54c2754..09836ff94fe2df 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/helpers.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/helpers.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { AfterKey, AfterKeys, IdentifierType } from '../../../common/risk_engine'; +import type { AfterKey, AfterKeys, IdentifierType } from '../../../../common/risk_engine'; import type { CalculateAndPersistScoresResponse } from './types'; export const getFieldForIdentifierAgg = (identifierType: IdentifierType): string => diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.mock.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/risk_engine_data_client.mock.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.mock.ts rename to x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/risk_engine_data_client.mock.ts diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.test.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/risk_engine_data_client.test.ts similarity index 99% rename from x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.test.ts rename to x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/risk_engine_data_client.test.ts index 8dc2da15c2e185..619952859fc0f2 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.test.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/risk_engine_data_client.test.ts @@ -67,7 +67,7 @@ jest.mock('./utils/create_datastream', () => ({ createDataStream: jest.fn(), })); -jest.mock('../risk_score/transform/helpers/transforms', () => ({ +jest.mock('../../risk_score/transform/helpers/transforms', () => ({ createAndStartTransform: jest.fn(), })); diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/risk_engine_data_client.ts similarity index 97% rename from x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.ts rename to x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/risk_engine_data_client.ts index 269f4b6dad9fd7..ea564ffe2395c5 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/risk_engine_data_client.ts @@ -26,13 +26,13 @@ import { import { createDataStream } from './utils/create_datastream'; import type { RiskEngineDataWriter as Writer } from './risk_engine_data_writer'; import { RiskEngineDataWriter } from './risk_engine_data_writer'; -import type { InitRiskEngineResult } from '../../../common/risk_engine'; +import type { InitRiskEngineResult } from '../../../../common/risk_engine'; import { RiskEngineStatus, getRiskScoreLatestIndex, MAX_SPACES_COUNT, RiskScoreEntity, -} from '../../../common/risk_engine'; +} from '../../../../common/risk_engine'; import { getLegacyTransforms, getLatestTransformId, @@ -48,7 +48,7 @@ import { import { getRiskInputsIndex } from './get_risk_inputs_index'; import { removeRiskScoringTask, startRiskScoringTask } from './tasks'; import { createIndex } from './utils/create_index'; -import { bulkDeleteSavedObjects } from '../risk_score/prebuilt_saved_objects/helpers/bulk_delete_saved_objects'; +import { bulkDeleteSavedObjects } from '../../risk_score/prebuilt_saved_objects/helpers/bulk_delete_saved_objects'; interface InitOpts { namespace: string; diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_writer.test.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/risk_engine_data_writer.test.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_writer.test.ts rename to x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/risk_engine_data_writer.test.ts diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_writer.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/risk_engine_data_writer.ts similarity index 97% rename from x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_writer.ts rename to x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/risk_engine_data_writer.ts index a897dd0462f693..8e859449bb32d2 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_writer.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/risk_engine_data_writer.ts @@ -7,7 +7,7 @@ import type { BulkOperationContainer } from '@elastic/elasticsearch/lib/api/types'; import type { Logger, ElasticsearchClient } from '@kbn/core/server'; -import type { IdentifierType, RiskScore } from '../../../common/risk_engine'; +import type { IdentifierType, RiskScore } from '../../../../common/risk_engine'; interface WriterBulkResponse { errors: string[]; diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/risk_score_service.mock.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/risk_score_service.mock.ts similarity index 94% rename from x-pack/plugins/security_solution/server/lib/risk_engine/risk_score_service.mock.ts rename to x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/risk_score_service.mock.ts index 54fd66c7d3e971..68b5b55dbda429 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/risk_score_service.mock.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/risk_score_service.mock.ts @@ -6,7 +6,7 @@ */ import type { RiskScoreService } from './risk_score_service'; -import type { RiskScore } from '../../../common/risk_engine'; +import type { RiskScore } from '../../../../common/risk_engine'; const createRiskScoreMock = (overrides: Partial = {}): RiskScore => ({ '@timestamp': '2023-02-15T00:15:19.231Z', diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/risk_score_service.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/risk_score_service.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/risk_engine/risk_score_service.ts rename to x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/risk_score_service.ts diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/risk_weights.test.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/risk_weights.test.ts similarity index 97% rename from x-pack/plugins/security_solution/server/lib/risk_engine/risk_weights.test.ts rename to x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/risk_weights.test.ts index 0bc25121771bd6..40b3ee800b1bda 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/risk_weights.test.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/risk_weights.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { RiskWeightTypes, RiskCategories } from '../../../common/risk_engine'; +import { RiskWeightTypes, RiskCategories } from '../../../../common/risk_engine'; import { buildCategoryAssignment, buildCategoryWeights, diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/risk_weights.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/risk_weights.ts similarity index 96% rename from x-pack/plugins/security_solution/server/lib/risk_engine/risk_weights.ts rename to x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/risk_weights.ts index 34ab491b74b048..225887f2dce55c 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/risk_weights.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/risk_weights.ts @@ -12,8 +12,8 @@ import type { RiskCategoryRiskWeight, RiskWeight, RiskWeights, -} from '../../../common/risk_engine'; -import { RiskCategories, RiskWeightTypes } from '../../../common/risk_engine'; +} from '../../../../common/risk_engine'; +import { RiskCategories, RiskWeightTypes } from '../../../../common/risk_engine'; const RISK_CATEGORIES = Object.values(RiskCategories); diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/index.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/index.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/risk_engine/routes/index.ts rename to x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/index.ts diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_disable_route.test.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/risk_engine_disable_route.test.ts similarity index 88% rename from x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_disable_route.test.ts rename to x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/risk_engine_disable_route.test.ts index e7c162ddd08e8c..23e58896199a90 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_disable_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/risk_engine_disable_route.test.ts @@ -8,15 +8,15 @@ import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { riskEngineDisableRoute } from './risk_engine_disable_route'; -import { RISK_ENGINE_DISABLE_URL } from '../../../../common/constants'; +import { RISK_ENGINE_DISABLE_URL } from '../../../../../common/constants'; import { serverMock, requestContextMock, requestMock, -} from '../../detection_engine/routes/__mocks__'; +} from '../../../detection_engine/routes/__mocks__'; import { riskEngineDataClientMock } from '../risk_engine_data_client.mock'; -describe('risk score calculation route', () => { +describe('risk score disable route', () => { let server: ReturnType; let context: ReturnType; let mockTaskManagerStart: ReturnType; @@ -78,7 +78,7 @@ describe('risk score calculation route', () => { const response = await server.inject(request, context); expect(response.status).toEqual(500); - expect(response.body.message.message).toEqual('something went wrong'); + expect(response.body.message).toEqual('something went wrong'); }); }); @@ -94,10 +94,8 @@ describe('risk score calculation route', () => { expect(response.status).toEqual(400); expect(response.body).toEqual({ - message: { - message: - 'Task Manager is unavailable, but is required to disable the risk engine. Please enable the taskManager plugin and try again.', - }, + message: + 'Task Manager is unavailable, but is required by the risk engine. Please enable the taskManager plugin and try again.', status_code: 400, }); }); diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_disable_route.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/risk_engine_disable_route.ts similarity index 80% rename from x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_disable_route.ts rename to x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/risk_engine_disable_route.ts index b5ae6287c40fd9..dde7ddbee9e83e 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_disable_route.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/risk_engine_disable_route.ts @@ -8,9 +8,10 @@ import type { StartServicesAccessor } from '@kbn/core/server'; import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils'; import { transformError } from '@kbn/securitysolution-es-utils'; -import { RISK_ENGINE_DISABLE_URL, APP_ID } from '../../../../common/constants'; -import type { StartPlugins } from '../../../plugin'; -import type { SecuritySolutionPluginRouter } from '../../../types'; +import { RISK_ENGINE_DISABLE_URL, APP_ID } from '../../../../../common/constants'; +import type { StartPlugins } from '../../../../plugin'; +import type { SecuritySolutionPluginRouter } from '../../../../types'; +import { TASK_MANAGER_UNAVAILABLE_ERROR } from './translations'; export const riskEngineDisableRoute = ( router: SecuritySolutionPluginRouter, @@ -34,10 +35,7 @@ export const riskEngineDisableRoute = ( if (!taskManager) { return siemResponse.error({ statusCode: 400, - body: { - message: - 'Task Manager is unavailable, but is required to disable the risk engine. Please enable the taskManager plugin and try again.', - }, + body: TASK_MANAGER_UNAVAILABLE_ERROR, }); } @@ -50,6 +48,7 @@ export const riskEngineDisableRoute = ( return siemResponse.error({ statusCode: error.statusCode, body: { message: error.message, full_error: JSON.stringify(e) }, + bypassErrorFormat: true, }); } }); diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_enable_route.test.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/risk_engine_enable_route.test.ts similarity index 88% rename from x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_enable_route.test.ts rename to x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/risk_engine_enable_route.test.ts index 8ef30ae60e368e..79a6c88c4fadfa 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_enable_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/risk_engine_enable_route.test.ts @@ -8,15 +8,15 @@ import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { riskEngineEnableRoute } from './risk_engine_enable_route'; -import { RISK_ENGINE_ENABLE_URL } from '../../../../common/constants'; +import { RISK_ENGINE_ENABLE_URL } from '../../../../../common/constants'; import { serverMock, requestContextMock, requestMock, -} from '../../detection_engine/routes/__mocks__'; +} from '../../../detection_engine/routes/__mocks__'; import { riskEngineDataClientMock } from '../risk_engine_data_client.mock'; -describe('risk score calculation route', () => { +describe('risk score enable route', () => { let server: ReturnType; let context: ReturnType; let mockTaskManagerStart: ReturnType; @@ -78,7 +78,7 @@ describe('risk score calculation route', () => { const response = await server.inject(request, context); expect(response.status).toEqual(500); - expect(response.body.message.message).toEqual('something went wrong'); + expect(response.body.message).toEqual('something went wrong'); }); }); @@ -94,10 +94,8 @@ describe('risk score calculation route', () => { expect(response.status).toEqual(400); expect(response.body).toEqual({ - message: { - message: - 'Task Manager is unavailable, but is required to enable the risk engine. Please enable the taskManager plugin and try again.', - }, + message: + 'Task Manager is unavailable, but is required by the risk engine. Please enable the taskManager plugin and try again.', status_code: 400, }); }); diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_enable_route.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/risk_engine_enable_route.ts similarity index 80% rename from x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_enable_route.ts rename to x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/risk_engine_enable_route.ts index af5eb77374bba7..bebc8b7236bb8d 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_enable_route.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/risk_engine_enable_route.ts @@ -8,9 +8,10 @@ import type { StartServicesAccessor } from '@kbn/core/server'; import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils'; import { transformError } from '@kbn/securitysolution-es-utils'; -import { RISK_ENGINE_ENABLE_URL, APP_ID } from '../../../../common/constants'; -import type { StartPlugins } from '../../../plugin'; -import type { SecuritySolutionPluginRouter } from '../../../types'; +import { RISK_ENGINE_ENABLE_URL, APP_ID } from '../../../../../common/constants'; +import { TASK_MANAGER_UNAVAILABLE_ERROR } from './translations'; +import type { StartPlugins } from '../../../../plugin'; +import type { SecuritySolutionPluginRouter } from '../../../../types'; export const riskEngineEnableRoute = ( router: SecuritySolutionPluginRouter, @@ -29,14 +30,10 @@ export const riskEngineEnableRoute = ( const [_, { taskManager }] = await getStartServices(); const securitySolution = await context.securitySolution; const riskEngineClient = securitySolution.getRiskEngineDataClient(); - if (!taskManager) { return siemResponse.error({ statusCode: 400, - body: { - message: - 'Task Manager is unavailable, but is required to enable the risk engine. Please enable the taskManager plugin and try again.', - }, + body: TASK_MANAGER_UNAVAILABLE_ERROR, }); } @@ -49,6 +46,7 @@ export const riskEngineEnableRoute = ( return siemResponse.error({ statusCode: error.statusCode, body: { message: error.message, full_error: JSON.stringify(e) }, + bypassErrorFormat: true, }); } }); diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_init_route.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/risk_engine_init_route.ts similarity index 86% rename from x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_init_route.ts rename to x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/risk_engine_init_route.ts index 2a0a5fafc70b05..e5b719dfbc42ff 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_init_route.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/risk_engine_init_route.ts @@ -8,10 +8,10 @@ import type { StartServicesAccessor } from '@kbn/core/server'; import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils'; import { transformError } from '@kbn/securitysolution-es-utils'; -import { RISK_ENGINE_INIT_URL, APP_ID } from '../../../../common/constants'; -import type { StartPlugins } from '../../../plugin'; - -import type { SecuritySolutionPluginRouter } from '../../../types'; +import { RISK_ENGINE_INIT_URL, APP_ID } from '../../../../../common/constants'; +import type { StartPlugins } from '../../../../plugin'; +import { TASK_MANAGER_UNAVAILABLE_ERROR } from './translations'; +import type { SecuritySolutionPluginRouter } from '../../../../types'; export const riskEngineInitRoute = ( router: SecuritySolutionPluginRouter, @@ -36,10 +36,7 @@ export const riskEngineInitRoute = ( if (!taskManager) { return siemResponse.error({ statusCode: 400, - body: { - message: - 'Task Manager is unavailable, but is required to initialize the risk engine. Please enable the taskManager plugin and try again.', - }, + body: TASK_MANAGER_UNAVAILABLE_ERROR, }); } @@ -67,6 +64,7 @@ export const riskEngineInitRoute = ( message: initResultResponse.errors.join('\n'), full_error: initResultResponse, }, + bypassErrorFormat: true, }); } return response.ok({ body: { result: initResultResponse } }); @@ -76,6 +74,7 @@ export const riskEngineInitRoute = ( return siemResponse.error({ statusCode: error.statusCode, body: { message: error.message, full_error: JSON.stringify(e) }, + bypassErrorFormat: true, }); } }); diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_status_route.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/risk_engine_status_route.ts similarity index 89% rename from x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_status_route.ts rename to x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/risk_engine_status_route.ts index d741ee5dd23ff5..a7d649c0d77845 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_status_route.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/risk_engine_status_route.ts @@ -7,9 +7,9 @@ import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils'; import { transformError } from '@kbn/securitysolution-es-utils'; -import { RISK_ENGINE_STATUS_URL, APP_ID } from '../../../../common/constants'; +import { RISK_ENGINE_STATUS_URL, APP_ID } from '../../../../../common/constants'; -import type { SecuritySolutionPluginRouter } from '../../../types'; +import type { SecuritySolutionPluginRouter } from '../../../../types'; export const riskEngineStatusRoute = (router: SecuritySolutionPluginRouter) => { router.versioned @@ -44,6 +44,7 @@ export const riskEngineStatusRoute = (router: SecuritySolutionPluginRouter) => { return siemResponse.error({ statusCode: error.statusCode, body: { message: error.message, full_error: JSON.stringify(e) }, + bypassErrorFormat: true, }); } }); diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_score_calculation_route.test.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/risk_score_calculation_route.test.ts similarity index 97% rename from x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_score_calculation_route.test.ts rename to x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/risk_score_calculation_route.test.ts index 2792727ae74f15..7cf7b5304a01d6 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_score_calculation_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/risk_score_calculation_route.test.ts @@ -9,12 +9,12 @@ import { riskScoreCalculationRoute } from './risk_score_calculation_route'; import { loggerMock } from '@kbn/logging-mocks'; -import { RISK_SCORE_CALCULATION_URL } from '../../../../common/constants'; +import { RISK_SCORE_CALCULATION_URL } from '../../../../../common/constants'; import { serverMock, requestContextMock, requestMock, -} from '../../detection_engine/routes/__mocks__'; +} from '../../../detection_engine/routes/__mocks__'; import { riskScoreServiceFactory } from '../risk_score_service'; import { riskScoreServiceMock } from '../risk_score_service.mock'; import { getRiskInputsIndex } from '../get_risk_inputs_index'; diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_score_calculation_route.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/risk_score_calculation_route.ts similarity index 88% rename from x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_score_calculation_route.ts rename to x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/risk_score_calculation_route.ts index 1b02c4a10fd254..cfbd80afb0d5fe 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_score_calculation_route.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/risk_score_calculation_route.ts @@ -12,10 +12,10 @@ import { APP_ID, DEFAULT_RISK_SCORE_PAGE_SIZE, RISK_SCORE_CALCULATION_URL, -} from '../../../../common/constants'; -import { riskScoreCalculationRequestSchema } from '../../../../common/risk_engine/risk_score_calculation/request_schema'; -import type { SecuritySolutionPluginRouter } from '../../../types'; -import { buildRouteValidation } from '../../../utils/build_validation/route_validation'; +} from '../../../../../common/constants'; +import { riskScoreCalculationRequestSchema } from '../../../../../common/risk_engine/risk_score_calculation/request_schema'; +import type { SecuritySolutionPluginRouter } from '../../../../types'; +import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; import { riskScoreServiceFactory } from '../risk_score_service'; import { getRiskInputsIndex } from '../get_risk_inputs_index'; @@ -89,6 +89,7 @@ export const riskScoreCalculationRoute = (router: SecuritySolutionPluginRouter, return siemResponse.error({ statusCode: error.statusCode, body: { message: error.message, full_error: JSON.stringify(e) }, + bypassErrorFormat: true, }); } } diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_score_preview_route.test.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/risk_score_preview_route.test.ts similarity index 97% rename from x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_score_preview_route.test.ts rename to x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/risk_score_preview_route.test.ts index 72963995594a81..ba87e94c3ccc2f 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_score_preview_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/risk_score_preview_route.test.ts @@ -7,13 +7,13 @@ import { loggerMock } from '@kbn/logging-mocks'; -import { RISK_SCORE_PREVIEW_URL } from '../../../../common/constants'; -import { RiskCategories, RiskWeightTypes } from '../../../../common/risk_engine'; +import { RISK_SCORE_PREVIEW_URL } from '../../../../../common/constants'; +import { RiskCategories, RiskWeightTypes } from '../../../../../common/risk_engine'; import { serverMock, requestContextMock, requestMock, -} from '../../detection_engine/routes/__mocks__'; +} from '../../../detection_engine/routes/__mocks__'; import { getRiskInputsIndex } from '../get_risk_inputs_index'; import { riskScoreServiceFactory } from '../risk_score_service'; import { riskScoreServiceMock } from '../risk_score_service.mock'; diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_score_preview_route.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/risk_score_preview_route.ts similarity index 89% rename from x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_score_preview_route.ts rename to x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/risk_score_preview_route.ts index 9a1a5f2b83d7cb..05f80c526a927a 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_score_preview_route.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/risk_score_preview_route.ts @@ -13,10 +13,10 @@ import { APP_ID, DEFAULT_RISK_SCORE_PAGE_SIZE, RISK_SCORE_PREVIEW_URL, -} from '../../../../common/constants'; -import { riskScorePreviewRequestSchema } from '../../../../common/risk_engine/risk_score_preview/request_schema'; -import type { SecuritySolutionPluginRouter } from '../../../types'; -import { buildRouteValidation } from '../../../utils/build_validation/route_validation'; +} from '../../../../../common/constants'; +import { riskScorePreviewRequestSchema } from '../../../../../common/risk_engine/risk_score_preview/request_schema'; +import type { SecuritySolutionPluginRouter } from '../../../../types'; +import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; import { riskScoreServiceFactory } from '../risk_score_service'; import { getRiskInputsIndex } from '../get_risk_inputs_index'; @@ -91,6 +91,7 @@ export const riskScorePreviewRoute = (router: SecuritySolutionPluginRouter, logg return siemResponse.error({ statusCode: error.statusCode, body: { message: error.message, full_error: JSON.stringify(e) }, + bypassErrorFormat: true, }); } } diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/translations.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/translations.ts new file mode 100644 index 00000000000000..648ec23ea6b3c7 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/translations.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 { i18n } from '@kbn/i18n'; + +export const TASK_MANAGER_UNAVAILABLE_ERROR = i18n.translate( + 'xpack.securitySolution.api.riskEngine.taskManagerUnavailable', + { + defaultMessage: + 'Task Manager is unavailable, but is required by the risk engine. Please enable the taskManager plugin and try again.', + } +); diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/saved_object/index.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/saved_object/index.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/risk_engine/saved_object/index.ts rename to x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/saved_object/index.ts diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/saved_object/risk_engine_configuration_type.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/saved_object/risk_engine_configuration_type.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/risk_engine/saved_object/risk_engine_configuration_type.ts rename to x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/saved_object/risk_engine_configuration_type.ts diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/schema/risk_score_apis.yml b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/schema/risk_score_apis.yml similarity index 100% rename from x-pack/plugins/security_solution/server/lib/risk_engine/schema/risk_score_apis.yml rename to x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/schema/risk_score_apis.yml diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/tasks/constants.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/tasks/constants.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/risk_engine/tasks/constants.ts rename to x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/tasks/constants.ts diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/tasks/helpers.test.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/tasks/helpers.test.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/risk_engine/tasks/helpers.test.ts rename to x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/tasks/helpers.test.ts diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/tasks/helpers.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/tasks/helpers.ts similarity index 97% rename from x-pack/plugins/security_solution/server/lib/risk_engine/tasks/helpers.ts rename to x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/tasks/helpers.ts index dfacb8b78f30ad..b2db9b348b55e1 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/tasks/helpers.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/tasks/helpers.ts @@ -14,7 +14,7 @@ import { } from '@kbn/core/server'; import { addSpaceIdToPath } from '@kbn/spaces-plugin/server'; -import type { Range } from '../../../../common/risk_engine'; +import type { Range } from '../../../../../common/risk_engine'; export const convertDateToISOString = (dateString: string): string => { const date = datemath.parse(dateString); diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/tasks/index.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/tasks/index.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/risk_engine/tasks/index.ts rename to x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/tasks/index.ts diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/tasks/risk_scoring_task.mock.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/tasks/risk_scoring_task.mock.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/risk_engine/tasks/risk_scoring_task.mock.ts rename to x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/tasks/risk_scoring_task.mock.ts diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/tasks/risk_scoring_task.test.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/tasks/risk_scoring_task.test.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/risk_engine/tasks/risk_scoring_task.test.ts rename to x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/tasks/risk_scoring_task.test.ts diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/tasks/risk_scoring_task.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/tasks/risk_scoring_task.ts similarity index 96% rename from x-pack/plugins/security_solution/server/lib/risk_engine/tasks/risk_scoring_task.ts rename to x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/tasks/risk_scoring_task.ts index b0482b43b5c134..525f2247b63bd4 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/tasks/risk_scoring_task.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/tasks/risk_scoring_task.ts @@ -18,8 +18,8 @@ import type { TaskManagerStartContract, } from '@kbn/task-manager-plugin/server'; import type { AnalyticsServiceSetup } from '@kbn/core-analytics-server'; -import type { AfterKeys, IdentifierType } from '../../../../common/risk_engine'; -import type { StartPlugins } from '../../../plugin'; +import type { AfterKeys, IdentifierType } from '../../../../../common/risk_engine'; +import type { StartPlugins } from '../../../../plugin'; import { type RiskScoreService, riskScoreServiceFactory } from '../risk_score_service'; import { RiskEngineDataClient } from '../risk_engine_data_client'; import { isRiskScoreCalculationComplete } from '../helpers'; @@ -30,12 +30,12 @@ import { } from './state'; import { INTERVAL, SCOPE, TIMEOUT, TYPE, VERSION } from './constants'; import { buildScopedInternalSavedObjectsClientUnsafe, convertRangeToISO } from './helpers'; -import { RiskScoreEntity } from '../../../../common/risk_engine/types'; +import { RiskScoreEntity } from '../../../../../common/risk_engine/types'; import { RISK_SCORE_EXECUTION_SUCCESS_EVENT, RISK_SCORE_EXECUTION_ERROR_EVENT, RISK_SCORE_EXECUTION_CANCELLATION_EVENT, -} from '../../telemetry/event_based/events'; +} from '../../../telemetry/event_based/events'; const logFactory = (logger: Logger, taskId: string) => diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/tasks/state.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/tasks/state.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/risk_engine/tasks/state.ts rename to x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/tasks/state.ts diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/types.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/types.ts similarity index 92% rename from x-pack/plugins/security_solution/server/lib/risk_engine/types.ts rename to x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/types.ts index 1e9751ff1388ea..f5aeaf4f56428a 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/types.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/types.ts @@ -14,7 +14,7 @@ import type { Range, RiskEngineStatus, RiskScore, -} from '../../../common/risk_engine'; +} from '../../../../common/risk_engine'; export interface CalculateScoresParams { afterKeys: AfterKeys; @@ -78,19 +78,15 @@ export interface InitRiskEngineResponse { export interface InitRiskEngineError { body: { - message: { - message: string; - full_error: InitRiskEngineResultResponse | undefined; - } & string; + message: string; + full_error: InitRiskEngineResultResponse | undefined; }; } export interface EnableDisableRiskEngineErrorResponse { body: { - message: { - message: string; - full_error: string; - }; + message: string; + full_error: string; }; } diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/utils/create_datastream.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/utils/create_datastream.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/risk_engine/utils/create_datastream.ts rename to x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/utils/create_datastream.ts diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/utils/create_index.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/utils/create_index.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/risk_engine/utils/create_index.ts rename to x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/utils/create_index.ts diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/utils/retry_transient_es_errors.test.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/utils/retry_transient_es_errors.test.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/risk_engine/utils/retry_transient_es_errors.test.ts rename to x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/utils/retry_transient_es_errors.test.ts diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/utils/retry_transient_es_errors.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/utils/retry_transient_es_errors.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/risk_engine/utils/retry_transient_es_errors.ts rename to x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/utils/retry_transient_es_errors.ts diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/utils/saved_object_configuration.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/utils/saved_object_configuration.ts similarity index 97% rename from x-pack/plugins/security_solution/server/lib/risk_engine/utils/saved_object_configuration.ts rename to x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/utils/saved_object_configuration.ts index 8d1562d805e576..e39f2f73e5df26 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/utils/saved_object_configuration.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/utils/saved_object_configuration.ts @@ -6,7 +6,7 @@ */ import type { SavedObject, SavedObjectsClientContract } from '@kbn/core/server'; -import { getAlertsIndex } from '../../../../common/utils/risk_score_modules'; +import { getAlertsIndex } from '../../../../../common/utils/risk_score_modules'; import type { RiskEngineConfiguration } from '../types'; import { riskEngineConfigurationTypeName } from '../saved_object'; diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/utils/transforms.test.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/utils/transforms.test.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/risk_engine/utils/transforms.test.ts rename to x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/utils/transforms.test.ts diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/utils/transforms.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/utils/transforms.ts similarity index 97% rename from x-pack/plugins/security_solution/server/lib/risk_engine/utils/transforms.ts rename to x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/utils/transforms.ts index d1a544233339e3..b78ea9ccfa6446 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/utils/transforms.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/utils/transforms.ts @@ -14,11 +14,11 @@ import type { TransformPutTransformRequest, TransformGetTransformStatsTransformStats, } from '@elastic/elasticsearch/lib/api/types'; -import { RiskScoreEntity } from '../../../../common/search_strategy'; +import { RiskScoreEntity } from '../../../../../common/search_strategy'; import { getRiskScorePivotTransformId, getRiskScoreLatestTransformId, -} from '../../../../common/utils/risk_score_modules'; +} from '../../../../../common/utils/risk_score_modules'; export const getLegacyTransforms = async ({ namespace, diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index adbf06cc1da99d..66f22e0c44bef9 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -107,7 +107,7 @@ import { } from '../common/endpoint/constants'; import { AppFeaturesService } from './lib/app_features_service/app_features_service'; -import { registerRiskScoringTask } from './lib/risk_engine/tasks/risk_scoring_task'; +import { registerRiskScoringTask } from './lib/entity_analytics/risk_engine/tasks/risk_scoring_task'; import { registerProtectionUpdatesNoteRoutes } from './endpoint/routes/protection_updates_note'; import { latestRiskScoreIndexPattern, allRiskScoreIndexPattern } from '../common/risk_engine'; import { isEndpointPackageV2 } from '../common/endpoint/utils/package_v2'; diff --git a/x-pack/plugins/security_solution/server/request_context_factory.ts b/x-pack/plugins/security_solution/server/request_context_factory.ts index be810fd5ae41ea..fde473d2a253e4 100644 --- a/x-pack/plugins/security_solution/server/request_context_factory.ts +++ b/x-pack/plugins/security_solution/server/request_context_factory.ts @@ -25,7 +25,7 @@ import type { import type { Immutable } from '../common/endpoint/types'; import type { EndpointAuthz } from '../common/endpoint/types/authz'; import type { EndpointAppContextService } from './endpoint/endpoint_app_context_services'; -import { RiskEngineDataClient } from './lib/risk_engine/risk_engine_data_client'; +import { RiskEngineDataClient } from './lib/entity_analytics/risk_engine/risk_engine_data_client'; export interface IRequestContextFactory { create( diff --git a/x-pack/plugins/security_solution/server/routes/index.ts b/x-pack/plugins/security_solution/server/routes/index.ts index d3786ea8acb88f..b5b6a5c205e955 100644 --- a/x-pack/plugins/security_solution/server/routes/index.ts +++ b/x-pack/plugins/security_solution/server/routes/index.ts @@ -80,8 +80,8 @@ import { riskEngineInitRoute, riskEngineEnableRoute, riskEngineStatusRoute, -} from '../lib/risk_engine/routes'; -import { riskScoreCalculationRoute } from '../lib/risk_engine/routes/risk_score_calculation_route'; +} from '../lib/entity_analytics/risk_engine/routes'; +import { riskScoreCalculationRoute } from '../lib/entity_analytics/risk_engine/routes/risk_score_calculation_route'; export const initRoutes = ( router: SecuritySolutionPluginRouter, diff --git a/x-pack/plugins/security_solution/server/saved_objects.ts b/x-pack/plugins/security_solution/server/saved_objects.ts index 3f91bcf149ac62..0b1fec56774880 100644 --- a/x-pack/plugins/security_solution/server/saved_objects.ts +++ b/x-pack/plugins/security_solution/server/saved_objects.ts @@ -14,7 +14,7 @@ import { legacyType as legacyRuleActionsType } from './lib/detection_engine/rule import { prebuiltRuleAssetType } from './lib/detection_engine/prebuilt_rules'; import { type as signalsMigrationType } from './lib/detection_engine/migrations/saved_objects'; import { manifestType } from './endpoint/lib/artifacts/saved_object_mappings'; -import { riskEngineConfigurationType } from './lib/risk_engine/saved_object'; +import { riskEngineConfigurationType } from './lib/entity_analytics/risk_engine/saved_object'; const types = [ noteType, diff --git a/x-pack/plugins/security_solution/server/types.ts b/x-pack/plugins/security_solution/server/types.ts index 8326d13ad03a74..c979cc25c172b0 100644 --- a/x-pack/plugins/security_solution/server/types.ts +++ b/x-pack/plugins/security_solution/server/types.ts @@ -29,7 +29,7 @@ import type { import type { FrameworkRequest } from './lib/framework'; import type { EndpointAuthz } from '../common/endpoint/types/authz'; import type { EndpointInternalFleetServicesInterface } from './endpoint/services/fleet'; -import type { RiskEngineDataClient } from './lib/risk_engine/risk_engine_data_client'; +import type { RiskEngineDataClient } from './lib/entity_analytics/risk_engine/risk_engine_data_client'; export { AppClient }; diff --git a/x-pack/plugins/serverless_observability/kibana.jsonc b/x-pack/plugins/serverless_observability/kibana.jsonc index 0c68668e473eac..692d721c20a30f 100644 --- a/x-pack/plugins/serverless_observability/kibana.jsonc +++ b/x-pack/plugins/serverless_observability/kibana.jsonc @@ -3,7 +3,7 @@ "id": "@kbn/serverless-observability", "owner": [ "@elastic/appex-sharedux", - "@elastic/apm-ui" + "@elastic/obs-ux-management-team" ], "description": "Serverless customizations for observability.", "plugin": { diff --git a/x-pack/plugins/serverless_search/kibana.jsonc b/x-pack/plugins/serverless_search/kibana.jsonc index 2ae8f0dbff9879..0ac92bc1974688 100644 --- a/x-pack/plugins/serverless_search/kibana.jsonc +++ b/x-pack/plugins/serverless_search/kibana.jsonc @@ -25,7 +25,9 @@ "share", "visualizations" ], - "optionalPlugins": [], + "optionalPlugins": [ + "indexManagement", + ], "requiredBundles": [ "kibanaReact" ] diff --git a/x-pack/plugins/serverless_search/public/application/components/connectors/empty_connectors_prompt.tsx b/x-pack/plugins/serverless_search/public/application/components/connectors/empty_connectors_prompt.tsx index 6496b6c357150e..a997c8086225f3 100644 --- a/x-pack/plugins/serverless_search/public/application/components/connectors/empty_connectors_prompt.tsx +++ b/x-pack/plugins/serverless_search/public/application/components/connectors/empty_connectors_prompt.tsx @@ -50,7 +50,7 @@ export const EmptyConnectorsPrompt: React.FC = () => {

    {i18n.translate('xpack.serverlessSearch.connectorsEmpty.description', { defaultMessage: - "To set up and deploy a connector you'll be working between the third-party data source, your terminal, and the Kibana UI. The high level process looks like this:", + "To set up and deploy a connector you'll be working between the third-party data source, your terminal, and the Elasticsearch serverless UI. The high level process looks like this:", })}

    diff --git a/x-pack/plugins/serverless_search/public/application/components/connectors_overview.tsx b/x-pack/plugins/serverless_search/public/application/components/connectors_overview.tsx index 1c766020b7b7c9..f79dce303617b0 100644 --- a/x-pack/plugins/serverless_search/public/application/components/connectors_overview.tsx +++ b/x-pack/plugins/serverless_search/public/application/components/connectors_overview.tsx @@ -82,9 +82,9 @@ export const ConnectorsOverview = () => { - + {i18n.translate('xpack.serverlessSearch.connectorsPythonLink', { - defaultMessage: 'connectors-python', + defaultMessage: 'elastic/connectors', })} diff --git a/x-pack/plugins/serverless_search/public/application/components/index_mappings_docs_link.tsx b/x-pack/plugins/serverless_search/public/application/components/index_mappings_docs_link.tsx new file mode 100644 index 00000000000000..fc1699beffa8f3 --- /dev/null +++ b/x-pack/plugins/serverless_search/public/application/components/index_mappings_docs_link.tsx @@ -0,0 +1,68 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FunctionComponent } from 'react'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiLink, + EuiPanel, + EuiSpacer, + EuiText, + EuiTitle, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { CoreStart } from '@kbn/core/public'; +import { IndexContent } from '@kbn/index-management-plugin/public/services'; + +const IndexMappingsDocsLink: FunctionComponent<{ docLinks: CoreStart['docLinks'] }> = ({ + docLinks, +}) => { + return ( + + + + + + + +

    + +

    +
    +
    +
    + + +

    + +

    +
    + + + + +
    + ); +}; + +export const createIndexMappingsDocsLinkContent = (core: CoreStart): IndexContent => { + return { + renderContent: () => , + }; +}; diff --git a/x-pack/plugins/serverless_search/public/application/components/languages/javascript.ts b/x-pack/plugins/serverless_search/public/application/components/languages/javascript.ts index 1302bebff71376..72d865f1030bcb 100644 --- a/x-pack/plugins/serverless_search/public/application/components/languages/javascript.ts +++ b/x-pack/plugins/serverless_search/public/application/components/languages/javascript.ts @@ -20,12 +20,15 @@ const searchResult = await client.search({ console.log(searchResult.hits.hits) `, - configureClient: ({ url, apiKey }) => `const { Client } = require('@elastic/elasticsearch'); + configureClient: ({ + url, + apiKey, + }) => `const { Client } = require('@elastic/elasticsearch-serverless'); const client = new Client({ -node: '${url}', -auth: { + node: '${url}', + auth: { apiKey: '${apiKey}' -} + } });`, docLink: docLinks.jsClient, github: { @@ -44,34 +47,34 @@ const dataset = [ {"name": "Fahrenheit 451", "author": "Ray Bradbury", "release_date": "1953-10-15", "page_count": 227}, {"name": "Brave New World", "author": "Aldous Huxley", "release_date": "1932-06-01", "page_count": 268}, {"name": "The Handmaid's Tale", "author": "Margaret Atwood", "release_date": "1985-06-01", "page_count": 311} - ]; +]; // Index with the bulk helper const result = await client.helpers.bulk({ -datasource: dataset, -onDocument (doc) { - return { index: { _index: 'my-index-name' }}; -} + datasource: dataset, + onDocument (doc) { + return { index: { _index: 'my-index-name' }}; + } }); console.log(result); /** { -total: 6, -failed: 0, -retry: 0, -successful: 6, -noop: 0, -time: 191, -bytes: 787, -aborted: false + total: 6, + failed: 0, + retry: 0, + successful: 6, + noop: 0, + time: 191, + bytes: 787, + aborted: false } */`, ingestDataIndex: ({ apiKey, url, indexName, - }) => `const { Client } = require('@elastic/elasticsearch'); + }) => `const { Client } = require('@elastic/elasticsearch-serverless'); const client = new Client({ node: '${url}', auth: { @@ -91,7 +94,7 @@ const result = await client.helpers.bulk({ }); console.log(result); `, - installClient: 'npm install @elastic/elasticsearch@8', + installClient: 'npm install @elastic/elasticsearch-serverless', name: i18n.translate('xpack.serverlessSearch.languages.javascript', { defaultMessage: 'JavaScript', }), @@ -100,20 +103,20 @@ console.log(result); console.log(resp); /** { -name: 'instance-0000000000', -cluster_name: 'd9dcd35d12fe46dfaa28ec813f65d57b', -cluster_uuid: 'iln8jaivThSezhTkzp0Knw', -version: { - build_flavor: 'default', - build_type: 'docker', - build_hash: 'c94b4700cda13820dad5aa74fae6db185ca5c304', - build_date: '2022-10-24T16:54:16.433628434Z', - build_snapshot: false, - lucene_version: '9.4.1', - minimum_wire_compatibility_version: '7.17.0', - minimum_index_compatibility_version: '7.0.0' -}, -tagline: 'You Know, for Search' + name: 'instance-0000000000', + cluster_name: 'd9dcd35d12fe46dfaa28ec813f65d57b', + cluster_uuid: 'iln8jaivThSezhTkzp0Knw', + version: { + build_flavor: 'default', + build_type: 'docker', + build_hash: 'c94b4700cda13820dad5aa74fae6db185ca5c304', + build_date: '2022-10-24T16:54:16.433628434Z', + build_snapshot: false, + lucene_version: '9.4.1', + minimum_wire_compatibility_version: '7.17.0', + minimum_index_compatibility_version: '7.0.0' + }, + tagline: 'You Know, for Search' } */`, }; diff --git a/x-pack/plugins/serverless_search/public/layout/nav.tsx b/x-pack/plugins/serverless_search/public/layout/nav.tsx index 3aaa1ae62884cf..7d4b2fd7d0fd19 100644 --- a/x-pack/plugins/serverless_search/public/layout/nav.tsx +++ b/x-pack/plugins/serverless_search/public/layout/nav.tsx @@ -15,6 +15,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import type { ServerlessPluginStart } from '@kbn/serverless/public'; import type { CloudStart } from '@kbn/cloud-plugin/public'; +import { CONNECTORS_LABEL } from '../../common/i18n_string'; const navigationTree: NavigationTreeDefinition = { body: [ @@ -37,79 +38,60 @@ const navigationTree: NavigationTreeDefinition = { getIsActive: ({ pathNameSerialized, prepend }) => { return pathNameSerialized.startsWith(prepend('/app/dev_tools')); }, + spaceBefore: 'l', }, { - id: 'explore', - title: i18n.translate('xpack.serverlessSearch.nav.explore', { - defaultMessage: 'Explore', + link: 'discover', + spaceBefore: 'm', + }, + { + link: 'dashboards', + getIsActive: ({ pathNameSerialized, prepend }) => { + return pathNameSerialized.startsWith(prepend('/app/dashboards')); + }, + }, + { + link: 'visualize', + title: i18n.translate('xpack.serverlessSearch.nav.visualize', { + defaultMessage: 'Visualizations', }), - children: [ - { - link: 'discover', - }, - { - link: 'dashboards', - getIsActive: ({ pathNameSerialized, prepend }) => { - return pathNameSerialized.startsWith(prepend('/app/dashboards')); - }, - }, - { - link: 'visualize', - title: i18n.translate('xpack.serverlessSearch.nav.visualize', { - defaultMessage: 'Visualizations', - }), - getIsActive: ({ pathNameSerialized, prepend }) => { - return ( - pathNameSerialized.startsWith(prepend('/app/visualize')) || - pathNameSerialized.startsWith(prepend('/app/lens')) || - pathNameSerialized.startsWith(prepend('/app/maps')) - ); - }, - }, - { - link: 'management:triggersActions', - title: i18n.translate('xpack.serverlessSearch.nav.alerts', { - defaultMessage: 'Alerts', - }), - }, - ], + getIsActive: ({ pathNameSerialized, prepend }) => { + return ( + pathNameSerialized.startsWith(prepend('/app/visualize')) || + pathNameSerialized.startsWith(prepend('/app/lens')) || + pathNameSerialized.startsWith(prepend('/app/maps')) + ); + }, }, { - id: 'content', - title: i18n.translate('xpack.serverlessSearch.nav.content', { - defaultMessage: 'Content', + link: 'management:triggersActions', + title: i18n.translate('xpack.serverlessSearch.nav.alerts', { + defaultMessage: 'Alerts', }), - children: [ - { - title: i18n.translate('xpack.serverlessSearch.nav.content.indices', { - defaultMessage: 'Index Management', - }), - link: 'management:index_management', - breadcrumbStatus: - 'hidden' /* management sub-pages set their breadcrumbs themselves */, - }, - { - title: i18n.translate('xpack.serverlessSearch.nav.content.pipelines', { - defaultMessage: 'Pipelines', - }), - link: 'management:ingest_pipelines', - breadcrumbStatus: - 'hidden' /* management sub-pages set their breadcrumbs themselves */, - }, - ], }, { - id: 'security', - title: i18n.translate('xpack.serverlessSearch.nav.security', { - defaultMessage: 'Security', + title: i18n.translate('xpack.serverlessSearch.nav.content.indices', { + defaultMessage: 'Index Management', }), - children: [ - { - link: 'management:api_keys', - breadcrumbStatus: - 'hidden' /* management sub-pages set their breadcrumbs themselves */, - }, - ], + link: 'management:index_management', + breadcrumbStatus: 'hidden' /* management sub-pages set their breadcrumbs themselves */, + spaceBefore: 'm', + }, + { + title: i18n.translate('xpack.serverlessSearch.nav.content.pipelines', { + defaultMessage: 'Pipelines', + }), + link: 'management:ingest_pipelines', + breadcrumbStatus: 'hidden' /* management sub-pages set their breadcrumbs themselves */, + }, + { + title: CONNECTORS_LABEL, + link: 'serverlessConnectors', + }, + { + link: 'management:api_keys', + breadcrumbStatus: 'hidden' /* management sub-pages set their breadcrumbs themselves */, + spaceBefore: 'm', }, ], }, diff --git a/x-pack/plugins/serverless_search/public/plugin.ts b/x-pack/plugins/serverless_search/public/plugin.ts index 6f1cb6465106c5..42039256503853 100644 --- a/x-pack/plugins/serverless_search/public/plugin.ts +++ b/x-pack/plugins/serverless_search/public/plugin.ts @@ -15,6 +15,7 @@ import { import { i18n } from '@kbn/i18n'; import { appIds } from '@kbn/management-cards-navigation'; import { AuthenticatedUser } from '@kbn/security-plugin/common'; +import { createIndexMappingsDocsLinkContent as createIndexMappingsContent } from './application/components/index_mappings_docs_link'; import { createServerlessSearchSideNavComponent as createComponent } from './layout/nav'; import { docLinks } from '../common/doc_links'; import { @@ -85,7 +86,7 @@ export class ServerlessSearchPlugin public start( core: CoreStart, - { serverless, management, cloud }: ServerlessSearchPluginStartDependencies + { serverless, management, cloud, indexManagement }: ServerlessSearchPluginStartDependencies ): ServerlessSearchPluginStart { serverless.setProjectHome('/app/elasticsearch'); serverless.setSideNavComponent(createComponent(core, { serverless, cloud })); @@ -94,6 +95,7 @@ export class ServerlessSearchPlugin enabled: true, hideLinksTo: [appIds.MAINTENANCE_WINDOWS], }); + indexManagement?.extensionsService.setIndexMappingsContent(createIndexMappingsContent(core)); return {}; } diff --git a/x-pack/plugins/serverless_search/public/types.ts b/x-pack/plugins/serverless_search/public/types.ts index 5b984289e2bd7e..039353fa4e8671 100644 --- a/x-pack/plugins/serverless_search/public/types.ts +++ b/x-pack/plugins/serverless_search/public/types.ts @@ -10,6 +10,7 @@ import { ManagementSetup, ManagementStart } from '@kbn/management-plugin/public' import { SecurityPluginStart } from '@kbn/security-plugin/public'; import { ServerlessPluginSetup, ServerlessPluginStart } from '@kbn/serverless/public'; import { SharePluginStart } from '@kbn/share-plugin/public'; +import { IndexManagementPluginStart } from '@kbn/index-management-plugin/public'; // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface ServerlessSearchPluginSetup {} @@ -29,4 +30,5 @@ export interface ServerlessSearchPluginStartDependencies { security: SecurityPluginStart; serverless: ServerlessPluginStart; share: SharePluginStart; + indexManagement?: IndexManagementPluginStart; } diff --git a/x-pack/plugins/serverless_search/tsconfig.json b/x-pack/plugins/serverless_search/tsconfig.json index 1886f0ccb21b9e..c07cff77aa19ee 100644 --- a/x-pack/plugins/serverless_search/tsconfig.json +++ b/x-pack/plugins/serverless_search/tsconfig.json @@ -36,5 +36,6 @@ "@kbn/search-connectors", "@kbn/shared-ux-router", "@kbn/kibana-utils-plugin", + "@kbn/index-management-plugin", ] } diff --git a/x-pack/plugins/session_view/public/components/detail_panel_alert_tab/index.test.tsx b/x-pack/plugins/session_view/public/components/detail_panel_alert_tab/index.test.tsx index 989c4fd45cfbcd..b729824478e2cc 100644 --- a/x-pack/plugins/session_view/public/components/detail_panel_alert_tab/index.test.tsx +++ b/x-pack/plugins/session_view/public/components/detail_panel_alert_tab/index.test.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { AppContextTestRender, createAppRootMockRenderer } from '../../test'; import { DetailPanelAlertTab } from '.'; import { mockAlerts } from '../../../common/mocks/constants/session_view_process.mock'; -import { fireEvent } from '@testing-library/dom'; +import { fireEvent } from '@testing-library/react'; import { INVESTIGATED_ALERT_TEST_ID, VIEW_MODE_TOGGLE, ALERTS_TAB_EMPTY_STATE_TEST_ID } from '.'; import { ALERT_LIST_ITEM_TEST_ID, diff --git a/x-pack/plugins/session_view/public/components/session_view_search_bar/index.test.tsx b/x-pack/plugins/session_view/public/components/session_view_search_bar/index.test.tsx index 8b3498f2a11b87..4bfd695530b432 100644 --- a/x-pack/plugins/session_view/public/components/session_view_search_bar/index.test.tsx +++ b/x-pack/plugins/session_view/public/components/session_view_search_bar/index.test.tsx @@ -10,7 +10,7 @@ import { processMock } from '../../../common/mocks/constants/session_view_proces import { AppContextTestRender, createAppRootMockRenderer } from '../../test'; import { SessionViewSearchBar } from '.'; import userEvent from '@testing-library/user-event'; -import { fireEvent } from '@testing-library/dom'; +import { fireEvent } from '@testing-library/react'; describe('SessionViewSearchBar component', () => { let render: () => ReturnType; diff --git a/x-pack/plugins/spaces/server/capabilities/capabilities_switcher.ts b/x-pack/plugins/spaces/server/capabilities/capabilities_switcher.ts index e3183185022f65..b5d9be07d91af7 100644 --- a/x-pack/plugins/spaces/server/capabilities/capabilities_switcher.ts +++ b/x-pack/plugins/spaces/server/capabilities/capabilities_switcher.ts @@ -63,14 +63,19 @@ function toggleDisabledFeatures( ) { const disabledFeatureKeys = activeSpace.disabledFeatures; - const [enabledFeatures, disabledFeatures] = features.reduce( + const { enabledFeatures, disabledFeatures } = features.reduce( (acc, feature) => { if (disabledFeatureKeys.includes(feature.id)) { - return [acc[0], [...acc[1], feature]]; + acc.disabledFeatures.push(feature); + } else { + acc.enabledFeatures.push(feature); } - return [[...acc[0], feature], acc[1]]; + return acc; }, - [[], []] as [KibanaFeature[], KibanaFeature[]] + { enabledFeatures: [], disabledFeatures: [] } as { + enabledFeatures: KibanaFeature[]; + disabledFeatures: KibanaFeature[]; + } ); const navLinks = capabilities.navLinks; diff --git a/x-pack/plugins/spaces/server/capabilities/index.ts b/x-pack/plugins/spaces/server/capabilities/index.ts index 745015b50042f4..801564d9990b8b 100644 --- a/x-pack/plugins/spaces/server/capabilities/index.ts +++ b/x-pack/plugins/spaces/server/capabilities/index.ts @@ -18,5 +18,7 @@ export const setupCapabilities = ( logger: Logger ) => { core.capabilities.registerProvider(capabilitiesProvider); - core.capabilities.registerSwitcher(setupCapabilitiesSwitcher(core, getSpacesService, logger)); + core.capabilities.registerSwitcher(setupCapabilitiesSwitcher(core, getSpacesService, logger), { + capabilityPath: '*', + }); }; diff --git a/x-pack/plugins/stack_connectors/README.md b/x-pack/plugins/stack_connectors/README.md index dc4265e92e0426..40199153902c0b 100644 --- a/x-pack/plugins/stack_connectors/README.md +++ b/x-pack/plugins/stack_connectors/README.md @@ -6,6 +6,7 @@ The `stack_connectors` plugin provides connector types shipped with Kibana, buil Table of Contents +- [Stack Connectors](#stack-connectors) - [Connector Types](#connector-types) - [ServiceNow ITSM](#servicenow-itsm) - [`params`](#params) @@ -41,13 +42,17 @@ Table of Contents - [Swimlane](#swimlane) - [`params`](#params-5) - [| severity | The severity of the incident. | string _(optional)_ |](#-severity-----the-severity-of-the-incident-----string-optional-) + - [Ospgenie](#ospgenie) + - [`params`](#params-6) + - [PagerDuty](#pagerduty) + - [`params`](#params-7) - [Developing New Connector Types](#developing-new-connector-types) - - [licensing](#licensing) - - [plugin location](#plugin-location) - - [documentation](#documentation) - - [tests](#tests) - - [connector type config and secrets](#connector-type-config-and-secrets) - - [user interface](#user-interface) + - [Licensing](#licensing) + - [Plugin location](#plugin-location) + - [Documentation](#documentation) + - [Tests](#tests) + - [Connector type config and secrets](#connector-type-config-and-secrets) + - [User interface](#user-interface) # Connector Types @@ -346,9 +351,9 @@ for the full list of properties. `subActionParams (createAlert)` -| Property | Description | Type | -| -------- | ------------------------------------------------------------------------------------------------------------- | --------------------- | -| message | The alert message. | string | +| Property | Description | Type | +| -------- | ------------------ | ------ | +| message | The alert message. | string | The optional parameters `alias`, `description`, `responders`, `visibleTo`, `actions`, `tags`, `details`, `entity`, `source`, `priority`, `user`, and `note` are supported. See the [Opsgenie API documentation](https://docs.opsgenie.com/docs/alert-api#create-alert) for more information on their types. @@ -358,6 +363,28 @@ No parameters are required. For the definition of the optional parameters see th --- +## PagerDuty + +The [PagerDuty user documentation `params`](https://www.elastic.co/guide/en/kibana/master/pagerduty-action-type.html) lists configuration properties for the `params`. For more details on these properties, see [PagerDuty v2 event parameters](https://developer.pagerduty.com/docs/ZG9jOjExMDI5NTgx-send-an-alert-event) . + +### `params` + +| Property | Description | Type | +| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------- | +| eventAction | The type of event. | `trigger` \| `resolve` \| `acknowledge` | +| dedupKey | All actions sharing this key will be associated with the same PagerDuty alert. This value is used to correlate trigger and resolution. The maximum length is 255 characters. | string | +| summary | An optional text summary of the event. The maximum length is 1024 characters. | string _(optional)_ | +| source | An optional value indicating the affected system, preferably a hostname or fully qualified domain name. Defaults to the Kibana saved object id of the action. | string _(optional)_ | +| severity | The perceived severity of on the affected system. Default: `info`. | `critical` \| `error` \| `warning` \| `info` | +| timestamp | An optional ISO-8601 format date-time, indicating the time the event was detected or generated. | date _(optional)_ | +| component | An optional value indicating the component of the source machine that is responsible for the event, for example `mysql` or `eth0`. | string _(optional)_ | +| group | An optional value indicating the logical grouping of components of a service, for example `app-stack`. | string _(optional)_ | +| class | An optional value indicating the class/type of the event, for example `ping failure` or `cpu load`. | string _(optional)_ | +| links | List of links to add to the event | Array<{ href: string; text: string }> _(optional)_ | +| customDetails | Additional details to add to the event. | object | + +--- + # Developing New Connector Types When creating a new connector type, your plugin will eventually call `server.plugins.actions.setup.registerType()` to register the type with the `actions` plugin, but there are some additional things to think about about and implement. diff --git a/x-pack/plugins/stack_connectors/public/connector_types/swimlane/swimlane_connectors.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/swimlane/swimlane_connectors.test.tsx index 643d43d8b10b37..cb6652052d65e1 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/swimlane/swimlane_connectors.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/swimlane/swimlane_connectors.test.tsx @@ -7,14 +7,12 @@ import React from 'react'; import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; -import { act } from 'react-dom/test-utils'; import SwimlaneActionConnectorFields from './swimlane_connectors'; import { useGetApplication } from './use_get_application'; import { applicationFields, mappings } from './mocks'; import { ConnectorFormTestProvider } from '../lib/test_utils'; -import { waitFor } from '@testing-library/dom'; +import { waitFor, render, act } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { render } from '@testing-library/react'; jest.mock('@kbn/triggers-actions-ui-plugin/public/common/lib/kibana'); jest.mock('./use_get_application'); diff --git a/x-pack/plugins/stack_connectors/server/connector_types/pagerduty/index.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/pagerduty/index.test.ts index 10752e53ae72a1..86cdca4740f6df 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/pagerduty/index.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/pagerduty/index.test.ts @@ -316,6 +316,25 @@ describe('execute()', () => { component: 'the-component', group: 'the-group', class: 'the-class', + customDetails: { + myString: 'foo', + myNumber: 10, + myArray: ['foo', 'baz'], + myBoolean: true, + myObject: { + myNestedObject: 'foo', + }, + }, + links: [ + { + href: 'http://example.com', + text: 'a link', + }, + { + href: 'http://example.com', + text: 'a second link', + }, + ], }; postPagerdutyMock.mockImplementation(() => { @@ -340,9 +359,31 @@ describe('execute()', () => { "data": Object { "dedup_key": "a-dedup-key", "event_action": "trigger", + "links": Array [ + Object { + "href": "http://example.com", + "text": "a link", + }, + Object { + "href": "http://example.com", + "text": "a second link", + }, + ], "payload": Object { "class": "the-class", "component": "the-component", + "custom_details": Object { + "myArray": Array [ + "foo", + "baz", + ], + "myBoolean": true, + "myNumber": 10, + "myObject": Object { + "myNestedObject": "foo", + }, + "myString": "foo", + }, "group": "the-group", "severity": "critical", "source": "the-source", @@ -383,6 +424,25 @@ describe('execute()', () => { component: 'the-component', group: 'the-group', class: 'the-class', + customDetails: { + myString: 'foo', + myNumber: 10, + myArray: ['foo', 'baz'], + myBoolean: true, + myObject: { + myNestedObject: 'foo', + }, + }, + links: [ + { + href: 'http://example.com', + text: 'a link', + }, + { + href: 'http://example.com', + text: 'a second link', + }, + ], }; postPagerdutyMock.mockImplementation(() => { @@ -441,6 +501,25 @@ describe('execute()', () => { component: 'the-component', group: 'the-group', class: 'the-class', + customDetails: { + myString: 'foo', + myNumber: 10, + myArray: ['foo', 'baz'], + myBoolean: true, + myObject: { + myNestedObject: 'foo', + }, + }, + links: [ + { + href: 'http://example.com', + text: 'a link', + }, + { + href: 'http://example.com', + text: 'a second link', + }, + ], }; postPagerdutyMock.mockImplementation(() => { diff --git a/x-pack/plugins/stack_connectors/server/connector_types/pagerduty/index.ts b/x-pack/plugins/stack_connectors/server/connector_types/pagerduty/index.ts index 76a19836b0cdad..6d2ee36e6439a0 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/pagerduty/index.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/pagerduty/index.ts @@ -79,6 +79,9 @@ const PayloadSeveritySchema = schema.oneOf([ schema.literal('info'), ]); +const LinksSchema = schema.arrayOf(schema.object({ href: schema.string(), text: schema.string() })); +const customDetailsSchema = schema.recordOf(schema.string(), schema.any()); + const ParamsSchema = schema.object( { eventAction: schema.maybe(EventActionSchema), @@ -90,6 +93,8 @@ const ParamsSchema = schema.object( component: schema.maybe(schema.string()), group: schema.maybe(schema.string()), class: schema.maybe(schema.string()), + links: schema.maybe(LinksSchema), + customDetails: schema.maybe(customDetailsSchema), }, { validate: validateParams } ); @@ -292,7 +297,9 @@ interface PagerDutyPayload { component?: string; group?: string; class?: string; + custom_details?: Record; }; + links?: Array<{ href: string; text: string }>; } function getBodyForEventAction(actionId: string, params: ActionParamsType): PagerDutyPayload { @@ -301,6 +308,7 @@ function getBodyForEventAction(actionId: string, params: ActionParamsType): Page const data: PagerDutyPayload = { event_action: eventAction, }; + if (params.dedupKey) { data.dedup_key = params.dedupKey; } @@ -318,7 +326,12 @@ function getBodyForEventAction(actionId: string, params: ActionParamsType): Page severity: params.severity || 'info', ...(validatedTimestamp ? { timestamp: moment(validatedTimestamp).toISOString() } : {}), ...omitBy(pick(params, ['component', 'group', 'class']), isUndefined), + ...(params.customDetails ? { custom_details: params.customDetails } : {}), }; + if (params.links) { + data.links = params.links; + } + return data; } diff --git a/x-pack/plugins/synthetics/common/runtime_types/monitor_management/monitor_types.ts b/x-pack/plugins/synthetics/common/runtime_types/monitor_management/monitor_types.ts index e109396965e7f6..27c55ade654c2a 100644 --- a/x-pack/plugins/synthetics/common/runtime_types/monitor_management/monitor_types.ts +++ b/x-pack/plugins/synthetics/common/runtime_types/monitor_management/monitor_types.ts @@ -321,21 +321,26 @@ export const SyntheticsMonitorWithIdCodec = t.intersection([ t.interface({ id: t.string }), ]); +const HeartbeatFieldsCodec = t.intersection([ + t.interface({ + config_id: t.string, + }), + t.partial({ + run_once: t.boolean, + test_run_id: t.string, + 'monitor.project.name': t.string, + 'monitor.id': t.string, + 'monitor.project.id': t.string, + 'monitor.fleet_managed': t.boolean, + meta: t.record(t.string, t.string), + }), +]); + export const HeartbeatConfigCodec = t.intersection([ SyntheticsMonitorWithIdCodec, t.partial({ fields_under_root: t.boolean, - fields: t.intersection([ - t.interface({ - config_id: t.string, - }), - t.partial({ - run_once: t.boolean, - test_run_id: t.string, - 'monitor.project.name': t.string, - 'monitor.project.id': t.string, - }), - ]), + fields: HeartbeatFieldsCodec, }), ]); @@ -400,6 +405,7 @@ export type BrowserFields = t.TypeOf; export type BrowserSimpleFields = t.TypeOf; export type BrowserAdvancedFields = t.TypeOf; export type MonitorFields = t.TypeOf; +export type HeartbeatFields = t.TypeOf; export type SyntheticsMonitor = t.TypeOf; export type SyntheticsMonitorWithId = t.TypeOf; export type EncryptedSyntheticsSavedMonitor = t.TypeOf; diff --git a/x-pack/plugins/synthetics/kibana.jsonc b/x-pack/plugins/synthetics/kibana.jsonc index d03d0d384938f5..9e85c2ec6c6043 100644 --- a/x-pack/plugins/synthetics/kibana.jsonc +++ b/x-pack/plugins/synthetics/kibana.jsonc @@ -1,7 +1,7 @@ { "type": "plugin", "id": "@kbn/synthetics-plugin", - "owner": "@elastic/uptime", + "owner": "@elastic/obs-ux-infra_services-team", "description": "This plugin visualizes data from Synthetics and Heartbeat, and integrates with other Observability solutions.", "plugin": { "id": "synthetics", diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/date_picker/synthetics_date_picker.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/date_picker/synthetics_date_picker.test.tsx index a78710dd9994e3..1a0ec3002655a4 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/date_picker/synthetics_date_picker.test.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/date_picker/synthetics_date_picker.test.tsx @@ -10,7 +10,7 @@ import { SyntheticsDatePicker } from './synthetics_date_picker'; import { startPlugins } from '../../../utils/testing/__mocks__/synthetics_plugin_start_mock'; import { createMemoryHistory } from 'history'; import { render } from '../../../utils/testing'; -import { fireEvent } from '@testing-library/dom'; +import { fireEvent } from '@testing-library/react'; describe('SyntheticsDatePicker component', () => { jest.setTimeout(10_000); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/simple_monitor_form.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/simple_monitor_form.test.tsx index 9c392b6a409f0a..322cc2c653ef9b 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/simple_monitor_form.test.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/simple_monitor_form.test.tsx @@ -12,10 +12,9 @@ import { WEBSITE_URL_HELP_TEXT, WEBSITE_URL_LABEL, } from './simple_monitor_form'; -import { screen } from '@testing-library/react'; import { render } from '../../utils/testing'; import React from 'react'; -import { act, fireEvent, waitFor } from '@testing-library/react'; +import { act, fireEvent, waitFor, screen } from '@testing-library/react'; import { syntheticsTestSubjects } from '../../../../../common/constants/data_test_subjects'; import { apiService } from '../../../../utils/api_service'; import * as reduxHooks from 'react-redux'; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/waterfall_marker/waterfall_marker_icon.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/waterfall_marker/waterfall_marker_icon.test.tsx index 3ebbb2b1c6b57a..a91da97ce3a5c6 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/waterfall_marker/waterfall_marker_icon.test.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/waterfall_marker/waterfall_marker_icon.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { fireEvent, waitFor } from '@testing-library/dom'; +import { fireEvent, waitFor } from '@testing-library/react'; import { WaterfallMarkerIcon } from './waterfall_marker_icon'; import { TestWrapper } from './waterfall_marker_test_helper'; import { render } from '../../../../../utils/testing'; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/rtl_helpers.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/rtl_helpers.tsx index 27452ce3254c2c..552229fe473463 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/rtl_helpers.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/rtl_helpers.tsx @@ -6,12 +6,14 @@ */ import React, { ReactElement, ReactNode } from 'react'; +import { i18n } from '@kbn/i18n'; import { of } from 'rxjs'; // eslint-disable-next-line import/no-extraneous-dependencies import { render as reactTestLibRender, MatcherFunction, RenderOptions, + configure, } from '@testing-library/react'; import { Router } from '@kbn/shared-ux-router'; import { Route } from '@kbn/shared-ux-router'; @@ -21,8 +23,6 @@ import { createMemoryHistory, History } from 'history'; import { CoreStart } from '@kbn/core/public'; import { I18nProvider } from '@kbn/i18n-react'; import { coreMock } from '@kbn/core/public/mocks'; -// eslint-disable-next-line import/no-extraneous-dependencies -import { configure } from '@testing-library/dom'; import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; import { KibanaContextProvider, KibanaServices } from '@kbn/kibana-react-plugin/public'; @@ -161,7 +161,13 @@ export const mockCore: () => Partial = () => { exploratoryView: { createExploratoryViewUrl: jest.fn(), getAppDataView: jest.fn(), - ExploratoryViewEmbeddable: () =>
    Embeddable exploratory view
    , + ExploratoryViewEmbeddable: () => ( +
    + {i18n.translate('xpack.synthetics.core.div.embeddableExploratoryViewLabel', { + defaultMessage: 'Embeddable exploratory view', + })} +
    + ), }, }; diff --git a/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor_project.ts b/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor_project.ts index e3ca890d1712e3..f878e8bb40c70e 100644 --- a/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor_project.ts +++ b/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor_project.ts @@ -102,8 +102,11 @@ export const validatePermissions = async ( const elasticManagedLocationsEnabled = Boolean( - (await server.coreStart?.capabilities.resolveCapabilities(request)).uptime - .elasticManagedLocationsEnabled + ( + await server.coreStart?.capabilities.resolveCapabilities(request, { + capabilityPath: 'uptime.*', + }) + ).uptime.elasticManagedLocationsEnabled ) ?? true; if (!elasticManagedLocationsEnabled) { return ELASTIC_MANAGED_LOCATIONS_DISABLED; diff --git a/x-pack/plugins/synthetics/server/routes/monitor_cruds/edit_monitor.ts b/x-pack/plugins/synthetics/server/routes/monitor_cruds/edit_monitor.ts index c6ff9852b67777..d9e9b918e3fffa 100644 --- a/x-pack/plugins/synthetics/server/routes/monitor_cruds/edit_monitor.ts +++ b/x-pack/plugins/synthetics/server/routes/monitor_cruds/edit_monitor.ts @@ -251,8 +251,11 @@ export const validatePermissions = async ( const elasticManagedLocationsEnabled = Boolean( - (await server.coreStart?.capabilities.resolveCapabilities(request)).uptime - .elasticManagedLocationsEnabled + ( + await server.coreStart?.capabilities.resolveCapabilities(request, { + capabilityPath: 'uptime.*', + }) + ).uptime.elasticManagedLocationsEnabled ) ?? true; if (!elasticManagedLocationsEnabled) { return ELASTIC_MANAGED_LOCATIONS_DISABLED; diff --git a/x-pack/plugins/synthetics/server/routes/monitor_cruds/get_api_key.ts b/x-pack/plugins/synthetics/server/routes/monitor_cruds/get_api_key.ts index 0dab70565aeea1..53cd56fc24bf61 100644 --- a/x-pack/plugins/synthetics/server/routes/monitor_cruds/get_api_key.ts +++ b/x-pack/plugins/synthetics/server/routes/monitor_cruds/get_api_key.ts @@ -34,8 +34,11 @@ export const getAPIKeySyntheticsRoute: SyntheticsRestApiRouteFactory = () => ({ if (accessToElasticManagedLocations) { const elasticManagedLocationsEnabled = Boolean( - (await server.coreStart?.capabilities.resolveCapabilities(request)).uptime - .elasticManagedLocationsEnabled + ( + await server.coreStart?.capabilities.resolveCapabilities(request, { + capabilityPath: 'uptime.*', + }) + ).uptime.elasticManagedLocationsEnabled ) ?? true; if (!elasticManagedLocationsEnabled) { return response.customError({ diff --git a/x-pack/plugins/synthetics/server/routes/monitor_cruds/get_monitor.ts b/x-pack/plugins/synthetics/server/routes/monitor_cruds/get_monitor.ts index 549c812eec27fb..b693718e7a63bb 100644 --- a/x-pack/plugins/synthetics/server/routes/monitor_cruds/get_monitor.ts +++ b/x-pack/plugins/synthetics/server/routes/monitor_cruds/get_monitor.ts @@ -52,7 +52,11 @@ export const getSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory = () => ({ } else { // only user with write permissions can decrypt the monitor const canSave = - (await coreStart?.capabilities.resolveCapabilities(request)).uptime.save ?? false; + ( + await coreStart?.capabilities.resolveCapabilities(request, { + capabilityPath: 'uptime.*', + }) + ).uptime.save ?? false; if (!canSave) { return response.forbidden(); } diff --git a/x-pack/plugins/synthetics/server/routes/monitor_cruds/inspect_monitor.ts b/x-pack/plugins/synthetics/server/routes/monitor_cruds/inspect_monitor.ts index 0cbdda79c18eb8..78f445190d2e93 100644 --- a/x-pack/plugins/synthetics/server/routes/monitor_cruds/inspect_monitor.ts +++ b/x-pack/plugins/synthetics/server/routes/monitor_cruds/inspect_monitor.ts @@ -54,8 +54,13 @@ export const inspectSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory = () = ); const canSave = - Boolean((await server.coreStart?.capabilities.resolveCapabilities(request)).uptime.save) ?? - false; + Boolean( + ( + await server.coreStart?.capabilities.resolveCapabilities(request, { + capabilityPath: 'uptime.*', + }) + ).uptime.save + ) ?? false; try { const newMonitorId = id ?? uuidV4(); diff --git a/x-pack/plugins/synthetics/server/routes/settings/params/params.ts b/x-pack/plugins/synthetics/server/routes/settings/params/params.ts index 8f9f4f76efd897..01f2dd6465dfd0 100644 --- a/x-pack/plugins/synthetics/server/routes/settings/params/params.ts +++ b/x-pack/plugins/synthetics/server/routes/settings/params/params.ts @@ -37,7 +37,11 @@ export const getSyntheticsParamsRoute: SyntheticsRestApiRouteFactory< const encryptedSavedObjectsClient = server.encryptedSavedObjects.getClient(); const canSave = - (await server.coreStart?.capabilities.resolveCapabilities(request)).uptime.save ?? false; + ( + await server.coreStart?.capabilities.resolveCapabilities(request, { + capabilityPath: 'uptime.*', + }) + ).uptime.save ?? false; if (canSave) { if (paramId) { diff --git a/x-pack/plugins/synthetics/server/routes/synthetics_service/get_service_locations.ts b/x-pack/plugins/synthetics/server/routes/synthetics_service/get_service_locations.ts index 24660c537a2389..a9142170c9e260 100644 --- a/x-pack/plugins/synthetics/server/routes/synthetics_service/get_service_locations.ts +++ b/x-pack/plugins/synthetics/server/routes/synthetics_service/get_service_locations.ts @@ -23,8 +23,11 @@ export const getServiceLocationsRoute: SyntheticsRestApiRouteFactory = () => ({ }): Promise => { const elasticManagedLocationsEnabled = Boolean( - (await server.coreStart?.capabilities.resolveCapabilities(request)).uptime - .elasticManagedLocationsEnabled + ( + await server.coreStart?.capabilities.resolveCapabilities(request, { + capabilityPath: 'uptime.*', + }) + ).uptime.elasticManagedLocationsEnabled ) ?? true; if (elasticManagedLocationsEnabled) { diff --git a/x-pack/plugins/synthetics/server/synthetics_service/formatters/private_formatters/format_synthetics_policy.ts b/x-pack/plugins/synthetics/server/synthetics_service/formatters/private_formatters/format_synthetics_policy.ts index 1b1d0838d73a99..f371747342281b 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/formatters/private_formatters/format_synthetics_policy.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/formatters/private_formatters/format_synthetics_policy.ts @@ -23,6 +23,7 @@ export interface ProcessorFields { 'monitor.id': string; test_run_id: string; run_once: boolean; + space_id: string; } export const formatSyntheticsPolicy = ( @@ -70,7 +71,7 @@ export const formatSyntheticsPolicy = ( const processorItem = dataStream?.vars?.processors; if (processorItem) { - processorItem.value = processorsFormatter(config); + processorItem.value = processorsFormatter(config as MonitorFields & ProcessorFields); } // TODO: remove this once we remove legacy support diff --git a/x-pack/plugins/synthetics/server/synthetics_service/formatters/private_formatters/processors_formatter.ts b/x-pack/plugins/synthetics/server/synthetics_service/formatters/private_formatters/processors_formatter.ts index 2705187acb5576..3a3aabb4e92d4d 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/formatters/private_formatters/processors_formatter.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/formatters/private_formatters/processors_formatter.ts @@ -6,43 +6,31 @@ */ import { ProcessorFields } from './format_synthetics_policy'; -import { MonitorFields } from '../../../../common/runtime_types'; - -type Fields = Record; +import { HeartbeatFields, MonitorFields } from '../../../../common/runtime_types'; interface FieldProcessor { add_fields: { target: string; - fields: Fields; + fields: HeartbeatFields; }; } -export const processorsFormatter = (config: Partial) => { - const fields: Fields = { - 'monitor.fleet_managed': true, - }; - if (config.test_run_id) { - fields.test_run_id = config.test_run_id; - } - if (config.run_once) { - fields.run_once = config.run_once; - } - if (config.config_id) { - fields.config_id = config.config_id; - } - if (config['monitor.project.name']) { - fields['monitor.project.name'] = config['monitor.project.name']; - } - if (config['monitor.project.id']) { - fields['monitor.project.id'] = config['monitor.project.id']; - } - if (config['monitor.id']) { - fields['monitor.id'] = config['monitor.id']; - } +export const processorsFormatter = (config: MonitorFields & ProcessorFields) => { const processors: FieldProcessor[] = [ { add_fields: { - fields, + fields: { + 'monitor.fleet_managed': true, + config_id: config.config_id, + test_run_id: config.test_run_id, + run_once: config.run_once, + 'monitor.id': config['monitor.id'], + 'monitor.project.name': config['monitor.project.name'], + 'monitor.project.id': config['monitor.project.id'], + meta: { + space_id: config.space_id, + }, + }, target: '', }, }, diff --git a/x-pack/plugins/synthetics/server/synthetics_service/formatters/public_formatters/format_configs.test.ts b/x-pack/plugins/synthetics/server/synthetics_service/formatters/public_formatters/format_configs.test.ts index ecb3a8408b55da..78756c04174bae 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/formatters/public_formatters/format_configs.test.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/formatters/public_formatters/format_configs.test.ts @@ -305,6 +305,7 @@ describe('formatHeartbeatRequest', () => { monitor: testBrowserConfig as SyntheticsMonitor, configId: monitorId, heartbeatId, + spaceId: 'test-space-id', }, '{"a":"param"}' ); @@ -317,6 +318,9 @@ describe('formatHeartbeatRequest', () => { 'monitor.project.id': testBrowserConfig.project_id, run_once: undefined, test_run_id: undefined, + meta: { + space_id: 'test-space-id', + }, }, fields_under_root: true, }); @@ -329,6 +333,7 @@ describe('formatHeartbeatRequest', () => { monitor: testBrowserConfig as SyntheticsMonitor, configId: monitorId, heartbeatId: monitorId, + spaceId: 'test-space-id', }, JSON.stringify({ key: 'value' }) ); @@ -341,6 +346,9 @@ describe('formatHeartbeatRequest', () => { 'monitor.project.id': testBrowserConfig.project_id, run_once: undefined, test_run_id: undefined, + meta: { + space_id: 'test-space-id', + }, }, fields_under_root: true, params: '{"key":"value"}', @@ -354,6 +362,7 @@ describe('formatHeartbeatRequest', () => { monitor, configId: monitorId, heartbeatId: monitorId, + spaceId: 'test-space-id', }); expect(actual).toEqual({ @@ -365,6 +374,9 @@ describe('formatHeartbeatRequest', () => { 'monitor.project.id': undefined, run_once: undefined, test_run_id: undefined, + meta: { + space_id: 'test-space-id', + }, }, fields_under_root: true, }); @@ -377,6 +389,7 @@ describe('formatHeartbeatRequest', () => { monitor, configId: monitorId, heartbeatId: monitorId, + spaceId: 'test-space-id', }); expect(actual).toEqual({ @@ -388,6 +401,9 @@ describe('formatHeartbeatRequest', () => { 'monitor.project.id': undefined, run_once: undefined, test_run_id: undefined, + meta: { + space_id: 'test-space-id', + }, }, fields_under_root: true, }); @@ -400,6 +416,7 @@ describe('formatHeartbeatRequest', () => { configId: monitorId, runOnce: true, heartbeatId: monitorId, + spaceId: 'test-space-id', }); expect(actual).toEqual({ @@ -411,6 +428,9 @@ describe('formatHeartbeatRequest', () => { 'monitor.project.id': testBrowserConfig.project_id, run_once: true, test_run_id: undefined, + meta: { + space_id: 'test-space-id', + }, }, fields_under_root: true, }); @@ -424,6 +444,7 @@ describe('formatHeartbeatRequest', () => { configId: monitorId, testRunId, heartbeatId: monitorId, + spaceId: 'test-space-id', }); expect(actual).toEqual({ @@ -435,6 +456,9 @@ describe('formatHeartbeatRequest', () => { 'monitor.project.id': testBrowserConfig.project_id, run_once: undefined, test_run_id: testRunId, + meta: { + space_id: 'test-space-id', + }, }, fields_under_root: true, }); @@ -448,6 +472,7 @@ describe('formatHeartbeatRequest', () => { configId: monitorId, testRunId, heartbeatId: monitorId, + spaceId: 'test-space-id', }); expect(actual).toEqual({ @@ -460,6 +485,9 @@ describe('formatHeartbeatRequest', () => { 'monitor.project.id': testBrowserConfig.project_id, run_once: undefined, test_run_id: testRunId, + meta: { + space_id: 'test-space-id', + }, }, fields_under_root: true, }); diff --git a/x-pack/plugins/synthetics/server/synthetics_service/formatters/public_formatters/format_configs.ts b/x-pack/plugins/synthetics/server/synthetics_service/formatters/public_formatters/format_configs.ts index 28149eac6ae529..e59a0b625337be 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/formatters/public_formatters/format_configs.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/formatters/public_formatters/format_configs.ts @@ -85,10 +85,11 @@ export interface ConfigData { runOnce?: boolean; testRunId?: string; params: Record; + spaceId: string; } export const formatHeartbeatRequest = ( - { monitor, configId, heartbeatId, runOnce, testRunId }: Omit, + { monitor, configId, heartbeatId, runOnce, testRunId, spaceId }: Omit, params?: string ): HeartbeatConfig => { const projectId = (monitor as BrowserFields)[ConfigKey.PROJECT_ID]; @@ -106,6 +107,9 @@ export const formatHeartbeatRequest = ( 'monitor.project.id': projectId || undefined, run_once: runOnce, test_run_id: testRunId, + meta: { + space_id: spaceId, + }, }, fields_under_root: true, params: monitor.type === 'browser' ? paramsString : '', diff --git a/x-pack/plugins/synthetics/server/synthetics_service/private_location/synthetics_private_location.ts b/x-pack/plugins/synthetics/server/synthetics_service/private_location/synthetics_private_location.ts index 7aa1b2570c68c4..418fbde0d31dbb 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/private_location/synthetics_private_location.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/private_location/synthetics_private_location.ts @@ -105,6 +105,7 @@ export class SyntheticsPrivateLocation { config.type, { ...(config as Partial), + space_id: spaceId, config_id: config.fields?.config_id, location_name: stringifyString(privateLocation.label), location_id: privateLocation.id, diff --git a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_monitor/synthetics_monitor_client.test.ts b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_monitor/synthetics_monitor_client.test.ts index a586114f00a81a..88884a65891f72 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_monitor/synthetics_monitor_client.test.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_monitor/synthetics_monitor_client.test.ts @@ -206,6 +206,7 @@ describe('SyntheticsMonitorClient', () => { params: { username: 'elastic', }, + spaceId: 'test-space', }, ]); expect(syntheticsService.deleteConfigs).toHaveBeenCalledTimes(1); diff --git a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_monitor/synthetics_monitor_client.ts b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_monitor/synthetics_monitor_client.ts index 696746e28a0f6f..da9be512cbcc83 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_monitor/synthetics_monitor_client.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_monitor/synthetics_monitor_client.ts @@ -111,6 +111,7 @@ export class SyntheticsMonitorClient { ); const configData = { + spaceId, params: paramsBySpace[spaceId], monitor: editedMonitor.monitor, configId: editedMonitor.id, @@ -128,7 +129,11 @@ export class SyntheticsMonitorClient { ); if (deletedPublicConfig) { - deletedPublicConfigs.push({ ...deletedPublicConfig, params: paramsBySpace[spaceId] }); + deletedPublicConfigs.push({ + ...deletedPublicConfig, + params: paramsBySpace[spaceId], + spaceId, + }); } if (privateLocations.length > 0 || this.hasPrivateLocations(editedMonitor.previousMonitor)) { @@ -165,7 +170,7 @@ export class SyntheticsMonitorClient { const privateDeletePromise = this.privateLocationAPI.deleteMonitors(monitors, spaceId); const publicDeletePromise = this.syntheticsService.deleteConfigs( - monitors.map((monitor) => ({ monitor, configId: monitor.config_id, params: {} })) + monitors.map((monitor) => ({ spaceId, monitor, configId: monitor.config_id, params: {} })) ); const [pubicResponse] = await Promise.all([publicDeletePromise, privateDeletePromise]); @@ -286,7 +291,7 @@ export class SyntheticsMonitorClient { for (const monitor of monitors) { const { publicLocations, privateLocations } = this.parseLocations(monitor); if (publicLocations.length > 0) { - publicConfigs.push({ monitor, configId: monitor.config_id, params: {} }); + publicConfigs.push({ spaceId, monitor, configId: monitor.config_id, params: {} }); } if (privateLocations.length > 0) { @@ -370,6 +375,7 @@ export class SyntheticsMonitorClient { heartbeatConfigs.push( formatHeartbeatRequest( { + spaceId, monitor: normalizedMonitor, configId: monitor.id, }, @@ -388,6 +394,7 @@ export class SyntheticsMonitorClient { ) { const { monitor, id } = monitorObj; const config = { + spaceId, monitor, configId: id, params: paramsBySpace[spaceId], diff --git a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts index 4f5739e933e0a1..aabedbe96443b7 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts @@ -640,6 +640,7 @@ export class SyntheticsService { monitor: normalizeSecrets(monitor).attributes, configId: monitor.id, heartbeatId: attributes[ConfigKey.MONITOR_QUERY_ID], + spaceId: monitorSpace, }; }); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row.test.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row.test.tsx index 370d7c2da05d5e..4f9eacc38ecc5c 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row.test.tsx @@ -5,14 +5,13 @@ * 2.0. */ -import { render, fireEvent, screen, waitFor } from '@testing-library/react'; +import { render, fireEvent, screen, waitFor, within } from '@testing-library/react'; import React from 'react'; import moment from 'moment-timezone'; import { TransformListRow } from '../../../../common'; import { ExpandedRow } from './expanded_row'; import transformListRow from '../../../../common/__mocks__/transform_list_row.json'; -import { within } from '@testing-library/dom'; jest.mock('../../../../../shared_imports'); jest.mock('../../../../app_dependencies'); diff --git a/x-pack/plugins/transform/server/capabilities.ts b/x-pack/plugins/transform/server/capabilities.ts index 5fd542d428a705..58d47cbb5ca419 100644 --- a/x-pack/plugins/transform/server/capabilities.ts +++ b/x-pack/plugins/transform/server/capabilities.ts @@ -127,50 +127,55 @@ export const setupCapabilities = ( }; }); - core.capabilities.registerSwitcher(async (request, capabilities, useDefaultCapabilities) => { - if (useDefaultCapabilities) { - return {}; - } - - const isSecurityPluginEnabled = securitySetup?.license.isEnabled() ?? false; - const startServices = await core.getStartServices(); - const [, { security: securityStart }] = startServices; + core.capabilities.registerSwitcher( + async (request, capabilities, useDefaultCapabilities) => { + if (useDefaultCapabilities) { + return {}; + } + + const isSecurityPluginEnabled = securitySetup?.license.isEnabled() ?? false; + const startServices = await core.getStartServices(); + const [, { security: securityStart }] = startServices; + + // If security is not enabled or not available, transform should have full permission + if (!isSecurityPluginEnabled || !securityStart) { + return { + transform: getInitialTransformCapabilities(true), + }; + } + + const checkPrivileges = securityStart.authz.checkPrivilegesDynamicallyWithRequest(request); + + const { hasAllRequested, privileges } = await checkPrivileges({ + elasticsearch: { + cluster: APP_CLUSTER_PRIVILEGES, + index: {}, + }, + }); + + const clusterPrivileges: Record = Array.isArray( + privileges?.elasticsearch?.cluster + ) + ? privileges.elasticsearch.cluster.reduce>((acc, p) => { + acc[p.privilege] = p.authorized; + return acc; + }, {}) + : {}; + + const hasOneIndexWithAllPrivileges = false; + + const transformCapabilities = getPrivilegesAndCapabilities( + clusterPrivileges, + hasOneIndexWithAllPrivileges, + hasAllRequested + ).capabilities; - // If security is not enabled or not available, transform should have full permission - if (!isSecurityPluginEnabled || !securityStart) { return { - transform: getInitialTransformCapabilities(true), + transform: transformCapabilities as Record>, }; + }, + { + capabilityPath: 'transform.*', } - - const checkPrivileges = securityStart.authz.checkPrivilegesDynamicallyWithRequest(request); - - const { hasAllRequested, privileges } = await checkPrivileges({ - elasticsearch: { - cluster: APP_CLUSTER_PRIVILEGES, - index: {}, - }, - }); - - const clusterPrivileges: Record = Array.isArray( - privileges?.elasticsearch?.cluster - ) - ? privileges.elasticsearch.cluster.reduce>((acc, p) => { - acc[p.privilege] = p.authorized; - return acc; - }, {}) - : {}; - - const hasOneIndexWithAllPrivileges = false; - - const transformCapabilities = getPrivilegesAndCapabilities( - clusterPrivileges, - hasOneIndexWithAllPrivileges, - hasAllRequested - ).capabilities; - - return { - transform: transformCapabilities as Record>, - }; - }); + ); }; diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index c06aff873c9bf6..e40475d4aae3d8 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -1204,7 +1204,6 @@ "dashboard.createConfirmModal.continueButtonLabel": "Poursuivre les modifications", "dashboard.createConfirmModal.unsavedChangesSubtitle": "Poursuivez les modifications ou utilisez un tableau de bord vierge.", "dashboard.createConfirmModal.unsavedChangesTitle": "Nouveau tableau de bord déjà en cours", - "dashboard.dashboardAppBreadcrumbsTitle": "Tableau de bord", "dashboard.dashboardPageTitle": "Tableaux de bord", "dashboard.dashboardWasSavedSuccessMessage": "Le tableau de bord \"{dashTitle}\" a été enregistré.", "dashboard.deleteError.toastDescription": "Erreur rencontrée lors de la suppression du tableau de bord", @@ -2245,7 +2244,6 @@ "discover.docTable.totalDocuments": "{totalDocuments} documents", "discover.dscTour.stepAddFields.description": "Cliquez sur {plusIcon} pour ajouter les champs qui vous intéressent.", "discover.dscTour.stepExpand.description": "Cliquez sur {expandIcon} pour afficher, comparer et filtrer les documents.", - "discover.errorCalloutFormattedTitle": "{title} : {errorMessage}", "discover.formatHit.moreFields": "et {count} autre(s) {count, plural, one {champ} many {champs} other {champs}}", "discover.howToSeeOtherMatchingDocumentsDescription": "Voici les {sampleSize} premiers documents correspondant à votre recherche. Veuillez affiner cette dernière pour en voir davantage.", "discover.noMatchRoute.bannerText": "L'application Discover ne reconnaît pas cet itinéraire : {route}", @@ -2368,7 +2366,6 @@ "discover.docTable.tableRow.viewSingleDocumentLinkText": "Afficher un seul document", "discover.docTable.tableRow.viewSurroundingDocumentsLinkText": "Afficher les documents alentour", "discover.documentsAriaLabel": "Documents", - "discover.documentsErrorTitle": "Erreur lors de la recherche", "discover.docViews.table.scoreSortWarningTooltip": "Filtrez sur _score pour pouvoir récupérer les valeurs correspondantes.", "discover.dropZoneTableLabel": "Abandonner la zone pour ajouter un champ en tant que colonne dans la table", "discover.dscTour.stepAddFields.imageAltText": "Dans la liste Champs disponibles, cliquez sur l'icône Plus pour afficher/masquer un champ dans le tableau de documents.", @@ -7958,7 +7955,7 @@ "xpack.aiops.logRateAnalysis.resultsTable.impactLabelColumnTooltip": "Le niveau d'impact du champ sur la différence de taux de messages.", "xpack.aiops.logRateAnalysis.resultsTable.linksMenu.copyToClipboardButtonLabel": "Copier dans le presse-papiers", "xpack.aiops.logRateAnalysis.resultsTable.linksMenu.copyToClipboardGroupMessage": "Copier les éléments de groupe en tant que syntaxe KQL dans le Presse-papiers", - "xpack.aiops.logRateAnalysis.resultsTable.linksMenu.copyToClipboardSignificantTermMessage": "Copier la paire clé-valeur en tant que syntaxe KQL dans le Presse-papiers", + "xpack.aiops.logRateAnalysis.resultsTable.linksMenu.copyToClipboardSignificantItemMessage": "Copier la paire clé-valeur en tant que syntaxe KQL dans le Presse-papiers", "xpack.aiops.logRateAnalysis.resultsTable.linksMenu.viewInDiscover": "Afficher dans Discover", "xpack.aiops.logRateAnalysis.resultsTable.linksMenu.viewInLogPatternAnalysis": "Voir dans l'analyse du modèle de log", "xpack.aiops.logRateAnalysis.resultsTable.logPatternLinkNotAvailableTooltipMessage": "Le lien n'est pas disponible si l'élément du tableau est lui-même un modèle de log.", @@ -9023,7 +9020,6 @@ "xpack.apm.mobile.charts.nct": "Type de connexion réseau", "xpack.apm.mobile.charts.noResultsFound": "Résultat introuvable", "xpack.apm.mobile.charts.osVersion": "Version du système d'exploitation", - "xpack.apm.mobile.coming.soon": "Bientôt disponible", "xpack.apm.mobile.filters.appVersion": "Version de l'application", "xpack.apm.mobile.filters.device": "Appareil", "xpack.apm.mobile.filters.nct": "NCT", @@ -29683,12 +29679,10 @@ "xpack.observability.slo.sloEdit.sliType.customMetric.equationHelpText": "Accepte les équations mathématiques de base, les caractères valides sont : A-Z, +, -, /, *, (, ), ?, !, &, :, |, >, <, =", "xpack.observability.slo.sloEdit.sliType.customMetric.equationLabel": "Équation", "xpack.observability.slo.sloEdit.sliType.customMetric.filterLabel": "Filtre", - "xpack.observability.slo.sloEdit.sliType.customMetric.goodQuery.tooltip": "Cette requête KQL doit renvoyer un sous-ensemble d'événements.", "xpack.observability.slo.sloEdit.sliType.customMetric.goodTitle": "Bons événements", "xpack.observability.slo.sloEdit.sliType.customMetric.metricField.placeholder": "Sélectionner un champ d’indicateur", "xpack.observability.slo.sloEdit.sliType.customMetric.metricLabel": "Indicateur", "xpack.observability.slo.sloEdit.sliType.customMetric.queryFilter": "Filtre de requête", - "xpack.observability.slo.sloEdit.sliType.customMetric.sumLabel": "Somme de", "xpack.observability.slo.sloEdit.sliType.customMetric.totalEquation.tooltip": "Ceci est compatible avec des calculs de base (A + B / C) et la logique booléenne (A < B ? A : B).", "xpack.observability.slo.sloEdit.sliType.customMetric.totalMetric.tooltip": "Les données de ce champ seront agrégées avec l’agréation de \"somme\".", "xpack.observability.slo.sloEdit.sliType.customMetric.totalTitle": "Total des événements", @@ -37250,16 +37244,13 @@ "xpack.serverlessSearch.languages.ruby.githubLabel": "elasticsearch-serverless-ruby", "xpack.serverlessSearch.learnMore": "En savoir plus", "xpack.serverlessSearch.nav.alerts": "Alertes", - "xpack.serverlessSearch.nav.content": "Contenu", "xpack.serverlessSearch.nav.content.indices": "Gestion des index", "xpack.serverlessSearch.nav.content.pipelines": "Pipelines", "xpack.serverlessSearch.nav.devTools": "Outils de développement", - "xpack.serverlessSearch.nav.explore": "Explorer", "xpack.serverlessSearch.nav.gettingStarted": "Premiers pas", "xpack.serverlessSearch.nav.mngt": "Gestion", "xpack.serverlessSearch.nav.performance": "Performances", "xpack.serverlessSearch.nav.projectSettings": "Paramètres de projet", - "xpack.serverlessSearch.nav.security": "Sécurité", "xpack.serverlessSearch.next": "Suivant", "xpack.serverlessSearch.optional": "Facultatif", "xpack.serverlessSearch.overview.footer.description": "Votre point de terminaison Elasticsearch est configuré et vous avez effectué quelques requêtes de base. Vous voilà prêt à approfondir les outils et les cas d'utilisation avancés.", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 5f27f5b60da131..8f58cdabe7785d 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -1218,7 +1218,6 @@ "dashboard.createConfirmModal.continueButtonLabel": "編集を続行", "dashboard.createConfirmModal.unsavedChangesSubtitle": "編集を続行するか、空のダッシュボードで始めてください。", "dashboard.createConfirmModal.unsavedChangesTitle": "新しいダッシュボードはすでに実行中です", - "dashboard.dashboardAppBreadcrumbsTitle": "ダッシュボード", "dashboard.dashboardPageTitle": "ダッシュボード", "dashboard.dashboardWasSavedSuccessMessage": "ダッシュボード「{dashTitle}」が保存されました。", "dashboard.deleteError.toastDescription": "ダッシュボードの削除中にエラーが発生しました", @@ -2259,7 +2258,6 @@ "discover.docTable.totalDocuments": "{totalDocuments}ドキュメント", "discover.dscTour.stepAddFields.description": "{plusIcon}をクリックして、関心があるフィールドを追加します。", "discover.dscTour.stepExpand.description": "{expandIcon}をクリックすると、ドキュメントを表示、比較、フィルタリングできます。", - "discover.errorCalloutFormattedTitle": "{title}:{errorMessage}", "discover.formatHit.moreFields": "およびその他{count}個の{count, plural, other {フィールド}}", "discover.howToSeeOtherMatchingDocumentsDescription": "これらは検索条件に一致した初めの{sampleSize}件のドキュメントです。他の結果を表示するには検索条件を絞ってください。", "discover.noMatchRoute.bannerText": "Discoverアプリケーションはこのルートを認識できません:{route}", @@ -2382,7 +2380,6 @@ "discover.docTable.tableRow.viewSingleDocumentLinkText": "単一のドキュメントを表示", "discover.docTable.tableRow.viewSurroundingDocumentsLinkText": "周りのドキュメントを表示", "discover.documentsAriaLabel": "ドキュメント", - "discover.documentsErrorTitle": "検索エラー", "discover.docViews.table.scoreSortWarningTooltip": "_scoreの値を取得するには、並べ替える必要があります。", "discover.dropZoneTableLabel": "フィールドを列として表に追加するには、ゾーンをドロップします", "discover.dscTour.stepAddFields.imageAltText": "[使用可能なフィールド]リストで、プラスアイコンをクリックし、フィールドをドキュメントテーブルに切り替えます。", @@ -7973,7 +7970,7 @@ "xpack.aiops.logRateAnalysis.resultsTable.impactLabelColumnTooltip": "メッセージレート差異に対するフィールドの影響のレベル。", "xpack.aiops.logRateAnalysis.resultsTable.linksMenu.copyToClipboardButtonLabel": "クリップボードにコピー", "xpack.aiops.logRateAnalysis.resultsTable.linksMenu.copyToClipboardGroupMessage": "グループアイテムをKQL構文としてクリップボードにコピー", - "xpack.aiops.logRateAnalysis.resultsTable.linksMenu.copyToClipboardSignificantTermMessage": "フィールド/値のペアをKQL構文としてクリップボードにコピー", + "xpack.aiops.logRateAnalysis.resultsTable.linksMenu.copyToClipboardSignificantItemMessage": "フィールド/値のペアをKQL構文としてクリップボードにコピー", "xpack.aiops.logRateAnalysis.resultsTable.linksMenu.viewInDiscover": "Discoverに表示", "xpack.aiops.logRateAnalysis.resultsTable.linksMenu.viewInLogPatternAnalysis": "ログパターン分析で表示", "xpack.aiops.logRateAnalysis.resultsTable.logPatternLinkNotAvailableTooltipMessage": "テーブル項目がログパターン自体の場合は、このリンクは使用できません。", @@ -9038,7 +9035,6 @@ "xpack.apm.mobile.charts.nct": "ネットワーク接続タイプ", "xpack.apm.mobile.charts.noResultsFound": "結果が見つかりませんでした", "xpack.apm.mobile.charts.osVersion": "OSバージョン", - "xpack.apm.mobile.coming.soon": "まもなくリリース", "xpack.apm.mobile.filters.appVersion": "アプリバージョン", "xpack.apm.mobile.filters.device": "デバイス", "xpack.apm.mobile.filters.nct": "NCT", @@ -29682,12 +29678,10 @@ "xpack.observability.slo.sloEdit.sliType.customMetric.equationHelpText": "基本的な数式をサポートします。有効な文字:A-Z、+、-、/、*、(、)、?、!、&、:、|、>、<、=", "xpack.observability.slo.sloEdit.sliType.customMetric.equationLabel": "式", "xpack.observability.slo.sloEdit.sliType.customMetric.filterLabel": "フィルター", - "xpack.observability.slo.sloEdit.sliType.customMetric.goodQuery.tooltip": "このKQLクエリはイベントのサブセットを返します。", "xpack.observability.slo.sloEdit.sliType.customMetric.goodTitle": "良好なイベント数", "xpack.observability.slo.sloEdit.sliType.customMetric.metricField.placeholder": "メトリックフィールドを選択", "xpack.observability.slo.sloEdit.sliType.customMetric.metricLabel": "メトリック", "xpack.observability.slo.sloEdit.sliType.customMetric.queryFilter": "クエリのフィルター", - "xpack.observability.slo.sloEdit.sliType.customMetric.sumLabel": "の合計", "xpack.observability.slo.sloEdit.sliType.customMetric.totalEquation.tooltip": "これは基本的な数学ロジック(A + B / C)とブールロジック(A < B ?A :B)をサポートします。", "xpack.observability.slo.sloEdit.sliType.customMetric.totalMetric.tooltip": "このフィールドのデータは「sum」集計で集約されます。", "xpack.observability.slo.sloEdit.sliType.customMetric.totalTitle": "合計イベント数", @@ -37248,16 +37242,13 @@ "xpack.serverlessSearch.languages.ruby.githubLabel": "elasticsearch-serverless-ruby", "xpack.serverlessSearch.learnMore": "詳細", "xpack.serverlessSearch.nav.alerts": "アラート", - "xpack.serverlessSearch.nav.content": "コンテンツ", "xpack.serverlessSearch.nav.content.indices": "インデックス管理", "xpack.serverlessSearch.nav.content.pipelines": "パイプライン", "xpack.serverlessSearch.nav.devTools": "開発ツール", - "xpack.serverlessSearch.nav.explore": "探索", "xpack.serverlessSearch.nav.gettingStarted": "はじめて使う", "xpack.serverlessSearch.nav.mngt": "管理", "xpack.serverlessSearch.nav.performance": "パフォーマンス", "xpack.serverlessSearch.nav.projectSettings": "プロジェクト設定", - "xpack.serverlessSearch.nav.security": "セキュリティ", "xpack.serverlessSearch.next": "次へ", "xpack.serverlessSearch.optional": "オプション", "xpack.serverlessSearch.overview.footer.description": "Elasticsearchエンドポイントが設定され、いくつかの基本的なクエリが作成されました。これで、より高度なツールやユースケースを使いこなす準備が整いました。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 7c8328a39f9873..62084ac55eed6d 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -1218,7 +1218,6 @@ "dashboard.createConfirmModal.continueButtonLabel": "继续编辑", "dashboard.createConfirmModal.unsavedChangesSubtitle": "继续编辑或使用空白仪表板从头开始。", "dashboard.createConfirmModal.unsavedChangesTitle": "新仪表板已在创建中", - "dashboard.dashboardAppBreadcrumbsTitle": "仪表板", "dashboard.dashboardPageTitle": "仪表板", "dashboard.dashboardWasSavedSuccessMessage": "仪表板“{dashTitle}”已保存", "dashboard.deleteError.toastDescription": "删除仪表板时发生错误", @@ -2259,7 +2258,6 @@ "discover.docTable.totalDocuments": "{totalDocuments} 个文档", "discover.dscTour.stepAddFields.description": "单击 {plusIcon} 以添加您感兴趣的字段。", "discover.dscTour.stepExpand.description": "单击 {expandIcon} 以查看、比较和筛选文档。", - "discover.errorCalloutFormattedTitle": "{title}:{errorMessage}", "discover.formatHit.moreFields": "及另外 {count} 个{count, plural, other {字段}}", "discover.howToSeeOtherMatchingDocumentsDescription": "以下是匹配您的搜索的前 {sampleSize} 个文档,请优化您的搜索以查看其他文档。", "discover.noMatchRoute.bannerText": "Discover 应用程序无法识别此路由:{route}", @@ -2382,7 +2380,6 @@ "discover.docTable.tableRow.viewSingleDocumentLinkText": "查看单个文档", "discover.docTable.tableRow.viewSurroundingDocumentsLinkText": "查看周围文档", "discover.documentsAriaLabel": "文档", - "discover.documentsErrorTitle": "搜索错误", "discover.docViews.table.scoreSortWarningTooltip": "要检索 _score 的值,必须按其筛选。", "discover.dropZoneTableLabel": "放置区域以将字段作为列添加到表中", "discover.dscTour.stepAddFields.imageAltText": "在可用字段列表中,单击加号图标将字段切换为文档表。", @@ -7972,7 +7969,7 @@ "xpack.aiops.logRateAnalysis.resultsTable.impactLabelColumnTooltip": "字段对消息速率差异的影响水平。", "xpack.aiops.logRateAnalysis.resultsTable.linksMenu.copyToClipboardButtonLabel": "复制到剪贴板", "xpack.aiops.logRateAnalysis.resultsTable.linksMenu.copyToClipboardGroupMessage": "将组项目作为 KQL 语法复制到剪贴板", - "xpack.aiops.logRateAnalysis.resultsTable.linksMenu.copyToClipboardSignificantTermMessage": "将字段/值对作为 KQL 语法复制到剪贴板", + "xpack.aiops.logRateAnalysis.resultsTable.linksMenu.copyToClipboardSignificantItemMessage": "将字段/值对作为 KQL 语法复制到剪贴板", "xpack.aiops.logRateAnalysis.resultsTable.linksMenu.viewInDiscover": "在 Discover 中查看", "xpack.aiops.logRateAnalysis.resultsTable.linksMenu.viewInLogPatternAnalysis": "在日志模式分析中查看", "xpack.aiops.logRateAnalysis.resultsTable.logPatternLinkNotAvailableTooltipMessage": "如果表项目为日志模式本身,则此链接不可用。", @@ -9037,7 +9034,6 @@ "xpack.apm.mobile.charts.nct": "网络连接类型", "xpack.apm.mobile.charts.noResultsFound": "找不到结果", "xpack.apm.mobile.charts.osVersion": "操作系统版本", - "xpack.apm.mobile.coming.soon": "即将推出", "xpack.apm.mobile.filters.appVersion": "应用版本", "xpack.apm.mobile.filters.device": "设备", "xpack.apm.mobile.filters.nct": "NCT", @@ -29680,12 +29676,10 @@ "xpack.observability.slo.sloEdit.sliType.customMetric.equationHelpText": "支持基本数学方程,有效字符包括:A-Z、+、-、/、*、(、)、?、!、&、:、|、>、<、=", "xpack.observability.slo.sloEdit.sliType.customMetric.equationLabel": "方程", "xpack.observability.slo.sloEdit.sliType.customMetric.filterLabel": "筛选", - "xpack.observability.slo.sloEdit.sliType.customMetric.goodQuery.tooltip": "此 KQL 查询应返回一个事件子集。", "xpack.observability.slo.sloEdit.sliType.customMetric.goodTitle": "良好事件", "xpack.observability.slo.sloEdit.sliType.customMetric.metricField.placeholder": "选择指标字段", "xpack.observability.slo.sloEdit.sliType.customMetric.metricLabel": "指标", "xpack.observability.slo.sloEdit.sliType.customMetric.queryFilter": "查询筛选", - "xpack.observability.slo.sloEdit.sliType.customMetric.sumLabel": "求和", "xpack.observability.slo.sloEdit.sliType.customMetric.totalEquation.tooltip": "这支持基本数学 (A + B / C) 和布尔逻辑 (A < B ?A :B)。", "xpack.observability.slo.sloEdit.sliType.customMetric.totalMetric.tooltip": "来自该字段的此类数据将使用“求和”聚合进行汇总。", "xpack.observability.slo.sloEdit.sliType.customMetric.totalTitle": "事件合计", @@ -37244,16 +37238,13 @@ "xpack.serverlessSearch.languages.ruby.githubLabel": "elasticsearch-serverless-ruby", "xpack.serverlessSearch.learnMore": "了解详情", "xpack.serverlessSearch.nav.alerts": "告警", - "xpack.serverlessSearch.nav.content": "内容", "xpack.serverlessSearch.nav.content.indices": "索引管理", "xpack.serverlessSearch.nav.content.pipelines": "管道", "xpack.serverlessSearch.nav.devTools": "开发工具", - "xpack.serverlessSearch.nav.explore": "浏览", "xpack.serverlessSearch.nav.gettingStarted": "入门", "xpack.serverlessSearch.nav.mngt": "管理", "xpack.serverlessSearch.nav.performance": "性能", "xpack.serverlessSearch.nav.projectSettings": "项目设置", - "xpack.serverlessSearch.nav.security": "安全", "xpack.serverlessSearch.next": "下一步", "xpack.serverlessSearch.optional": "可选", "xpack.serverlessSearch.overview.footer.description": "已设置您的 Elasticsearch 终端,并且您已提出一些基本查询。现在您已准备就绪,可以更深入地了解更多高级工具和用例。", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_get_query_delay_setting.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_get_query_delay_setting.test.tsx index bf792e29ed6046..c4e3ec261e6e32 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_get_query_delay_setting.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_get_query_delay_setting.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; import { renderHook } from '@testing-library/react-hooks/dom'; -import { waitFor } from '@testing-library/dom'; +import { waitFor } from '@testing-library/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { useGetQueryDelaySettings } from './use_get_query_delay_settings'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rule_aggregations.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rule_aggregations.test.tsx index b9f33ab40c6fc0..6ea281765496fe 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rule_aggregations.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rule_aggregations.test.tsx @@ -11,7 +11,7 @@ import { RuleStatus } from '../../types'; import { useKibana } from '../../common/lib/kibana'; import { IToasts } from '@kbn/core-notifications-browser'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { waitFor } from '@testing-library/dom'; +import { waitFor } from '@testing-library/react'; jest.mock('../../common/lib/kibana'); jest.mock('../lib/rule_api/aggregate_kuery_filter', () => ({ diff --git a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rules.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rules.test.tsx index 13427462238898..1a2c07eaaa9cfe 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rules.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rules.test.tsx @@ -15,7 +15,7 @@ import { RuleStatus } from '../../types'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { useKibana } from '../../common/lib/kibana'; import { IToasts } from '@kbn/core-notifications-browser'; -import { waitFor } from '@testing-library/dom'; +import { waitFor } from '@testing-library/react'; jest.mock('../../common/lib/kibana'); jest.mock('../lib/rule_api/rules_kuery_filter', () => ({ diff --git a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_tags_query.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_tags_query.test.tsx index 7e36b5e4290ad6..bb2dab67c0d4ee 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_tags_query.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_tags_query.test.tsx @@ -10,7 +10,7 @@ import { useLoadTagsQuery } from './use_load_tags_query'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { useKibana } from '../../common/lib/kibana'; import { IToasts } from '@kbn/core-notifications-browser'; -import { waitFor } from '@testing-library/dom'; +import { waitFor } from '@testing-library/react'; const MOCK_TAGS = ['a', 'b', 'c']; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_update_rules_settings.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_update_rules_settings.test.tsx index 6a36a32ed25229..e431a4c2cf7aae 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_update_rules_settings.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_update_rules_settings.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { act, renderHook } from '@testing-library/react-hooks/dom'; -import { waitFor } from '@testing-library/dom'; +import { waitFor } from '@testing-library/react'; import { useUpdateRuleSettings } from './use_update_rules_settings'; const mockAddDanger = jest.fn(); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_form.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_form.test.tsx index 95e7b0c85ab55b..cf12e2ce38af4e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_form.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_form.test.tsx @@ -9,7 +9,7 @@ import React, { lazy } from 'react'; import { ConnectorForm } from './connector_form'; import { actionTypeRegistryMock } from '../../action_type_registry.mock'; import userEvent from '@testing-library/user-event'; -import { waitFor } from '@testing-library/dom'; +import { waitFor } from '@testing-library/react'; import { act } from '@testing-library/react'; import { AppMockRenderer, createAppMockRenderer } from '../test_utils'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_form_fields.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_form_fields.test.tsx index c2a6a86bb446b8..45937cee8b22a7 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_form_fields.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_form_fields.test.tsx @@ -10,7 +10,7 @@ import { coreMock } from '@kbn/core/public/mocks'; import { FormTestProvider } from '../../components/test_utils'; import { ConnectorFormFields } from './connector_form_fields'; import { actionTypeRegistryMock } from '../../action_type_registry.mock'; -import { waitFor } from '@testing-library/dom'; +import { waitFor } from '@testing-library/react'; import { AppMockRenderer, createAppMockRenderer } from '../test_utils'; describe('ConnectorFormFields', () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/create_connector_flyout/index.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/create_connector_flyout/index.test.tsx index 9cc4603025ff34..554fb9aff3c306 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/create_connector_flyout/index.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/create_connector_flyout/index.test.tsx @@ -9,8 +9,7 @@ import React, { lazy } from 'react'; import { actionTypeRegistryMock } from '../../../action_type_registry.mock'; import userEvent from '@testing-library/user-event'; -import { waitFor } from '@testing-library/dom'; -import { act } from '@testing-library/react'; +import { waitFor, act } from '@testing-library/react'; import CreateConnectorFlyout from '.'; import { betaBadgeProps } from '../beta_badge_props'; import { AppMockRenderer, createAppMockRenderer } from '../../test_utils'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/edit_connector_flyout/index.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/edit_connector_flyout/index.test.tsx index e687189ed0205b..a108de1dc6f6c2 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/edit_connector_flyout/index.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/edit_connector_flyout/index.test.tsx @@ -9,8 +9,7 @@ import React, { lazy } from 'react'; import { actionTypeRegistryMock } from '../../../action_type_registry.mock'; import userEvent from '@testing-library/user-event'; -import { waitFor } from '@testing-library/dom'; -import { act } from '@testing-library/react'; +import { waitFor, act } from '@testing-library/react'; import EditConnectorFlyout from '.'; import { ActionConnector, EditConnectorTabs, GenericValidationResult } from '../../../../types'; import { betaBadgeProps } from '../beta_badge_props'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_bulk_get_cases.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_bulk_get_cases.test.tsx index 5f027e9b57fd00..945028df8118f1 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_bulk_get_cases.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_bulk_get_cases.test.tsx @@ -7,7 +7,7 @@ import { renderHook } from '@testing-library/react-hooks'; import * as api from './apis/bulk_get_cases'; -import { waitFor } from '@testing-library/dom'; +import { waitFor } from '@testing-library/react'; import { useKibana } from '../../../../common/lib/kibana'; import { useBulkGetCases } from './use_bulk_get_cases'; import { AppMockRenderer, createAppMockRenderer } from '../../test_utils'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_bulk_get_maintenance_windows.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_bulk_get_maintenance_windows.test.ts index e5b45a25b5dea1..b46424c286c001 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_bulk_get_maintenance_windows.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_bulk_get_maintenance_windows.test.ts @@ -6,7 +6,7 @@ */ import { renderHook } from '@testing-library/react-hooks'; -import { waitFor } from '@testing-library/dom'; +import { waitFor } from '@testing-library/react'; import { MaintenanceWindowStatus } from '@kbn/alerting-plugin/common'; import * as api from './apis/bulk_get_maintenance_windows'; import { coreMock } from '@kbn/core/public/mocks'; diff --git a/x-pack/plugins/uptime/kibana.jsonc b/x-pack/plugins/uptime/kibana.jsonc index df0b2e13839cfd..c07d0dc342a740 100644 --- a/x-pack/plugins/uptime/kibana.jsonc +++ b/x-pack/plugins/uptime/kibana.jsonc @@ -1,7 +1,7 @@ { "type": "plugin", "id": "@kbn/uptime-plugin", - "owner": "@elastic/uptime", + "owner": "@elastic/obs-ux-infra_services-team", "description": "This plugin visualizes data from Heartbeat, and integrates with other Observability solutions.", "plugin": { "id": "uptime", diff --git a/x-pack/plugins/uptime/public/legacy_uptime/components/common/uptime_date_picker.test.tsx b/x-pack/plugins/uptime/public/legacy_uptime/components/common/uptime_date_picker.test.tsx index 1510fe28f17213..66fd680d3fdfed 100644 --- a/x-pack/plugins/uptime/public/legacy_uptime/components/common/uptime_date_picker.test.tsx +++ b/x-pack/plugins/uptime/public/legacy_uptime/components/common/uptime_date_picker.test.tsx @@ -10,7 +10,7 @@ import { UptimeDatePicker } from './uptime_date_picker'; import { startPlugins } from '../../lib/__mocks__/uptime_plugin_start_mock'; import { createMemoryHistory } from 'history'; import { render } from '../../lib/helper/rtl_helpers'; -import { fireEvent } from '@testing-library/dom'; +import { fireEvent } from '@testing-library/react'; describe('UptimeDatePicker component', () => { jest.setTimeout(10_000); diff --git a/x-pack/plugins/uptime/public/legacy_uptime/components/monitor/synthetics/waterfall/components/waterfall.test.tsx b/x-pack/plugins/uptime/public/legacy_uptime/components/monitor/synthetics/waterfall/components/waterfall.test.tsx index 28e82930f33417..7761fe4206fda9 100644 --- a/x-pack/plugins/uptime/public/legacy_uptime/components/monitor/synthetics/waterfall/components/waterfall.test.tsx +++ b/x-pack/plugins/uptime/public/legacy_uptime/components/monitor/synthetics/waterfall/components/waterfall.test.tsx @@ -11,7 +11,7 @@ import { renderLegendItem } from '../../step_detail/waterfall/waterfall_chart_wr import { render } from '../../../../../lib/helper/rtl_helpers'; import 'jest-canvas-mock'; -import { waitFor } from '@testing-library/dom'; +import { waitFor } from '@testing-library/react'; describe('waterfall', () => { it('sets the correct height in case of full height', () => { diff --git a/x-pack/plugins/uptime/public/legacy_uptime/components/monitor/synthetics/waterfall/components/waterfall_marker_icon.test.tsx b/x-pack/plugins/uptime/public/legacy_uptime/components/monitor/synthetics/waterfall/components/waterfall_marker_icon.test.tsx index 4241a7238ecd69..4bfe9d2dccfdd3 100644 --- a/x-pack/plugins/uptime/public/legacy_uptime/components/monitor/synthetics/waterfall/components/waterfall_marker_icon.test.tsx +++ b/x-pack/plugins/uptime/public/legacy_uptime/components/monitor/synthetics/waterfall/components/waterfall_marker_icon.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { fireEvent, waitFor } from '@testing-library/dom'; +import { fireEvent, waitFor } from '@testing-library/react'; import { render } from '../../../../../lib/helper/rtl_helpers'; import { WaterfallMarkerIcon } from './waterfall_marker_icon'; import { TestWrapper } from './waterfall_marker_test_helper'; diff --git a/x-pack/plugins/uptime/public/legacy_uptime/components/overview/alerts/monitor_status_alert/alert_monitor_status.test.tsx b/x-pack/plugins/uptime/public/legacy_uptime/components/overview/alerts/monitor_status_alert/alert_monitor_status.test.tsx index 6dbef12159a18c..c547b66748ffc1 100644 --- a/x-pack/plugins/uptime/public/legacy_uptime/components/overview/alerts/monitor_status_alert/alert_monitor_status.test.tsx +++ b/x-pack/plugins/uptime/public/legacy_uptime/components/overview/alerts/monitor_status_alert/alert_monitor_status.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { screen } from '@testing-library/dom'; +import { screen } from '@testing-library/react'; import { AlertMonitorStatusComponent, AlertMonitorStatusProps, diff --git a/x-pack/plugins/uptime/public/legacy_uptime/components/overview/monitor_list/columns/define_connectors.test.tsx b/x-pack/plugins/uptime/public/legacy_uptime/components/overview/monitor_list/columns/define_connectors.test.tsx index 29f0dd189594c3..3774e94e190b8d 100644 --- a/x-pack/plugins/uptime/public/legacy_uptime/components/overview/monitor_list/columns/define_connectors.test.tsx +++ b/x-pack/plugins/uptime/public/legacy_uptime/components/overview/monitor_list/columns/define_connectors.test.tsx @@ -7,8 +7,7 @@ import React from 'react'; import { DefineAlertConnectors } from './define_connectors'; -import { screen } from '@testing-library/react'; -import { fireEvent } from '@testing-library/dom'; +import { screen, fireEvent } from '@testing-library/react'; import { ENABLE_STATUS_ALERT } from './translations'; import { render } from '../../../../lib/helper/rtl_helpers'; diff --git a/x-pack/plugins/uptime/public/legacy_uptime/components/overview/monitor_list/columns/enable_alert.test.tsx b/x-pack/plugins/uptime/public/legacy_uptime/components/overview/monitor_list/columns/enable_alert.test.tsx index 416dd8e963e9bb..2f18c3e89f1479 100644 --- a/x-pack/plugins/uptime/public/legacy_uptime/components/overview/monitor_list/columns/enable_alert.test.tsx +++ b/x-pack/plugins/uptime/public/legacy_uptime/components/overview/monitor_list/columns/enable_alert.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { EnableMonitorAlert } from './enable_alert'; -import { fireEvent } from '@testing-library/dom'; +import { fireEvent } from '@testing-library/react'; import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../../../../common/constants'; import { makePing } from '../../../../../../common/runtime_types/ping'; diff --git a/x-pack/plugins/uptime/public/legacy_uptime/components/synthetics/check_steps/use_expanded_row.test.tsx b/x-pack/plugins/uptime/public/legacy_uptime/components/synthetics/check_steps/use_expanded_row.test.tsx index f79a93eee53e45..a139abe5d86a05 100644 --- a/x-pack/plugins/uptime/public/legacy_uptime/components/synthetics/check_steps/use_expanded_row.test.tsx +++ b/x-pack/plugins/uptime/public/legacy_uptime/components/synthetics/check_steps/use_expanded_row.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import ReactRouterDom from 'react-router-dom'; import { Route } from '@kbn/shared-ux-router'; -import { fireEvent, screen } from '@testing-library/dom'; +import { fireEvent, screen } from '@testing-library/react'; import { renderHook, act as hooksAct } from '@testing-library/react-hooks'; import { createMemoryHistory } from 'history'; import { EuiButtonIcon } from '@elastic/eui'; diff --git a/x-pack/plugins/uptime/public/legacy_uptime/lib/helper/rtl_helpers.tsx b/x-pack/plugins/uptime/public/legacy_uptime/lib/helper/rtl_helpers.tsx index b2f96c4bac8a39..0b23c089385d7c 100644 --- a/x-pack/plugins/uptime/public/legacy_uptime/lib/helper/rtl_helpers.tsx +++ b/x-pack/plugins/uptime/public/legacy_uptime/lib/helper/rtl_helpers.tsx @@ -12,6 +12,7 @@ import { render as reactTestLibRender, MatcherFunction, RenderOptions, + configure, } from '@testing-library/react'; import { Router } from '@kbn/shared-ux-router'; import { Route } from '@kbn/shared-ux-router'; @@ -21,8 +22,6 @@ import { createMemoryHistory, History } from 'history'; import { CoreStart } from '@kbn/core/public'; import { I18nProvider } from '@kbn/i18n-react'; import { coreMock } from '@kbn/core/public/mocks'; -// eslint-disable-next-line import/no-extraneous-dependencies -import { configure } from '@testing-library/dom'; import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; import { KibanaContextProvider, KibanaServices } from '@kbn/kibana-react-plugin/public'; diff --git a/x-pack/plugins/uptime/public/legacy_uptime/pages/settings.test.tsx b/x-pack/plugins/uptime/public/legacy_uptime/pages/settings.test.tsx index 5c8fb95448fca0..a8144f540a03ec 100644 --- a/x-pack/plugins/uptime/public/legacy_uptime/pages/settings.test.tsx +++ b/x-pack/plugins/uptime/public/legacy_uptime/pages/settings.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { isValidCertVal, SettingsPage } from './settings'; import { render } from '../lib/helper/rtl_helpers'; -import { fireEvent, waitFor } from '@testing-library/dom'; +import { fireEvent, waitFor } from '@testing-library/react'; import * as alertApi from '../state/api/alerts'; import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../common/constants'; diff --git a/x-pack/plugins/ux/kibana.jsonc b/x-pack/plugins/ux/kibana.jsonc index af8d92f1517863..e35cddbabbba62 100644 --- a/x-pack/plugins/ux/kibana.jsonc +++ b/x-pack/plugins/ux/kibana.jsonc @@ -1,7 +1,7 @@ { "type": "plugin", "id": "@kbn/ux-plugin", - "owner": "@elastic/uptime", + "owner": "@elastic/obs-ux-infra_services-team", "plugin": { "id": "ux", "server": true, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/pagerduty.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/pagerduty.ts index 81a3c2ef966b93..7148ac962c728d 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/pagerduty.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/pagerduty.ts @@ -175,6 +175,47 @@ export default function pagerdutyTest({ getService }: FtrProviderContext) { }); }); + it('should execute successfully with links and customDetails', async () => { + const { body: result } = await supertest + .post(`/api/actions/connector/${simulatedActionId}/_execute`) + .set('kbn-xsrf', 'foo') + .send({ + params: { + summary: 'just a test', + customDetails: { + myString: 'foo', + myNumber: 10, + myArray: ['foo', 'baz'], + myBoolean: true, + myObject: { + myNestedObject: 'foo', + }, + }, + links: [ + { + href: 'http://example.com', + text: 'a link', + }, + { + href: 'http://example.com', + text: 'a second link', + }, + ], + }, + }) + .expect(200); + + expect(proxyHaveBeenCalled).to.equal(true); + expect(result).to.eql({ + status: 'ok', + connector_id: simulatedActionId, + data: { + message: 'Event processed', + status: 'success', + }, + }); + }); + it('should handle a 40x pagerduty error', async () => { const { body: result } = await supertest .post(`/api/actions/connector/${simulatedActionId}/_execute`) diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/migrations.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/migrations.ts index 4f23a5ff3a7278..4c555aba0e2dca 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/migrations.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/migrations.ts @@ -15,7 +15,8 @@ export default function createGetTests({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); - describe('migrations', () => { + // FLAKY: https://github.com/elastic/kibana/issues/169159 + describe.skip('migrations', () => { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/actions'); }); diff --git a/x-pack/test/api_integration/apis/aiops/log_rate_analysis_full_analysis.ts b/x-pack/test/api_integration/apis/aiops/log_rate_analysis_full_analysis.ts index c9fe22a472f4fc..3168dfdfc5ecfa 100644 --- a/x-pack/test/api_integration/apis/aiops/log_rate_analysis_full_analysis.ts +++ b/x-pack/test/api_integration/apis/aiops/log_rate_analysis_full_analysis.ts @@ -10,13 +10,19 @@ import fetch from 'node-fetch'; import { format as formatUrl } from 'url'; import expect from '@kbn/expect'; -import type { AiopsApiLogRateAnalysis } from '@kbn/aiops-plugin/common/api'; +import type { AiopsLogRateAnalysisSchema } from '@kbn/aiops-plugin/common/api/log_rate_analysis/schema'; import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common'; import type { FtrProviderContext } from '../../ftr_provider_context'; import { parseStream } from './parse_stream'; -import { logRateAnalysisTestData } from './test_data'; +import { getLogRateAnalysisTestData, API_VERSIONS } from './test_data'; +import { + getAddSignificationItemsActions, + getHistogramActions, + getGroupActions, + getGroupHistogramActions, +} from './test_helpers'; export default ({ getService }: FtrProviderContext) => { const aiops = getService('aiops'); @@ -26,202 +32,202 @@ export default ({ getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); describe('POST /internal/aiops/log_rate_analysis - full analysis', () => { - logRateAnalysisTestData.forEach((testData) => { - describe(`with ${testData.testName}`, () => { - before(async () => { - if (testData.esArchive) { - await esArchiver.loadIfNeeded(testData.esArchive); - } else if (testData.dataGenerator) { - await aiops.logRateAnalysisDataGenerator.generateData(testData.dataGenerator); - } - }); - - after(async () => { - if (testData.esArchive) { - await esArchiver.unload(testData.esArchive); - } else if (testData.dataGenerator) { - await aiops.logRateAnalysisDataGenerator.removeGeneratedData(testData.dataGenerator); - } - }); - - async function assertAnalysisResult(data: any[]) { - expect(data.length).to.eql( - testData.expected.actionsLength, - `Expected 'actionsLength' to be ${testData.expected.actionsLength}, got ${data.length}.` - ); - data.forEach((d) => { - expect(typeof d.type).to.be('string'); + API_VERSIONS.forEach((apiVersion) => { + getLogRateAnalysisTestData().forEach((testData) => { + describe(`with v${apiVersion} - ${testData.testName}`, () => { + before(async () => { + if (testData.esArchive) { + await esArchiver.loadIfNeeded(testData.esArchive); + } else if (testData.dataGenerator) { + await aiops.logRateAnalysisDataGenerator.generateData(testData.dataGenerator); + } }); - const addSignificantTermsActions = data.filter( - (d) => d.type === testData.expected.significantTermFilter - ); - expect(addSignificantTermsActions.length).to.greaterThan(0); - - const significantTerms = orderBy( - addSignificantTermsActions.flatMap((d) => d.payload), - ['doc_count'], - ['desc'] - ); - - expect(significantTerms).to.eql( - testData.expected.significantTerms, - 'Significant terms do not match expected values.' - ); - - const histogramActions = data.filter((d) => d.type === testData.expected.histogramFilter); - const histograms = histogramActions.flatMap((d) => d.payload); - // for each significant term we should get a histogram - expect(histogramActions.length).to.be(significantTerms.length); - // each histogram should have a length of 20 items. - histograms.forEach((h, index) => { - expect(h.histogram.length).to.be(20); + after(async () => { + if (testData.esArchive) { + await esArchiver.unload(testData.esArchive); + } else if (testData.dataGenerator) { + await aiops.logRateAnalysisDataGenerator.removeGeneratedData(testData.dataGenerator); + } }); - const groupActions = data.filter((d) => d.type === testData.expected.groupFilter); - const groups = groupActions.flatMap((d) => d.payload); - - expect(orderBy(groups, ['docCount'], ['desc'])).to.eql( - orderBy(testData.expected.groups, ['docCount'], ['desc']), - 'Grouping result does not match expected values.' - ); - - const groupHistogramActions = data.filter( - (d) => d.type === testData.expected.groupHistogramFilter - ); - const groupHistograms = groupHistogramActions.flatMap((d) => d.payload); - // for each significant terms group we should get a histogram - expect(groupHistograms.length).to.be(groups.length); - // each histogram should have a length of 20 items. - groupHistograms.forEach((h, index) => { - expect(h.histogram.length).to.be(20); - }); - } - - async function requestWithoutStreaming(body: AiopsApiLogRateAnalysis['body']) { - const resp = await supertest - .post(`/internal/aiops/log_rate_analysis`) - .set('kbn-xsrf', 'kibana') - .set(ELASTIC_HTTP_VERSION_HEADER, '1') - .send(body) - .expect(200); - - // compression is on by default so if the request body is undefined - // the response header should include "gzip" and otherwise be "undefined" - if (body.compressResponse === undefined) { - expect(resp.header['content-encoding']).to.be('gzip'); - } else if (body.compressResponse === false) { - expect(resp.header['content-encoding']).to.be(undefined); + async function assertAnalysisResult(data: any[]) { + expect(data.length).to.eql( + testData.expected.actionsLength, + `Expected 'actionsLength' to be ${testData.expected.actionsLength}, got ${data.length}.` + ); + data.forEach((d) => { + expect(typeof d.type).to.be('string'); + }); + + const addSignificantItemsActions = getAddSignificationItemsActions(data, apiVersion); + expect(addSignificantItemsActions.length).to.greaterThan(0); + + const significantItems = orderBy( + addSignificantItemsActions.flatMap((d) => d.payload), + ['doc_count'], + ['desc'] + ); + + expect(significantItems).to.eql( + testData.expected.significantItems, + 'Significant items do not match expected values.' + ); + + const histogramActions = getHistogramActions(data, apiVersion); + const histograms = histogramActions.flatMap((d) => d.payload); + // for each significant term we should get a histogram + expect(histogramActions.length).to.be(significantItems.length); + // each histogram should have a length of 20 items. + histograms.forEach((h, index) => { + expect(h.histogram.length).to.be(20); + }); + + const groupActions = getGroupActions(data, apiVersion); + const groups = groupActions.flatMap((d) => d.payload); + + expect(orderBy(groups, ['docCount'], ['desc'])).to.eql( + orderBy(testData.expected.groups, ['docCount'], ['desc']), + 'Grouping result does not match expected values.' + ); + + const groupHistogramActions = getGroupHistogramActions(data, apiVersion); + const groupHistograms = groupHistogramActions.flatMap((d) => d.payload); + // for each significant terms group we should get a histogram + expect(groupHistograms.length).to.be(groups.length); + // each histogram should have a length of 20 items. + groupHistograms.forEach((h, index) => { + expect(h.histogram.length).to.be(20); + }); } - expect(Buffer.isBuffer(resp.body)).to.be(true); + async function requestWithoutStreaming( + body: AiopsLogRateAnalysisSchema + ) { + const resp = await supertest + .post(`/internal/aiops/log_rate_analysis`) + .set('kbn-xsrf', 'kibana') + .set(ELASTIC_HTTP_VERSION_HEADER, apiVersion) + .send(body) + .expect(200); + + // compression is on by default so if the request body is undefined + // the response header should include "gzip" and otherwise be "undefined" + if (body.compressResponse === undefined) { + expect(resp.header['content-encoding']).to.be('gzip'); + } else if (body.compressResponse === false) { + expect(resp.header['content-encoding']).to.be(undefined); + } - const chunks: string[] = resp.body.toString().split('\n'); + expect(Buffer.isBuffer(resp.body)).to.be(true); - expect(chunks.length).to.eql( - testData.expected.chunksLength, - `Expected 'chunksLength' to be ${testData.expected.chunksLength}, got ${chunks.length}.` - ); + const chunks: string[] = resp.body.toString().split('\n'); - const lastChunk = chunks.pop(); - expect(lastChunk).to.be(''); + expect(chunks.length).to.eql( + testData.expected.chunksLength, + `Expected 'chunksLength' to be ${testData.expected.chunksLength}, got ${chunks.length}.` + ); - let data: any[] = []; + const lastChunk = chunks.pop(); + expect(lastChunk).to.be(''); - expect(() => { - data = chunks.map((c) => JSON.parse(c)); - }).not.to.throwError(); + let data: any[] = []; - await assertAnalysisResult(data); - } + expect(() => { + data = chunks.map((c) => JSON.parse(c)); + }).not.to.throwError(); - it('should return full data without streaming with compression with flushFix', async () => { - await requestWithoutStreaming(testData.requestBody); - }); + await assertAnalysisResult(data); + } - it('should return full data without streaming with compression without flushFix', async () => { - await requestWithoutStreaming({ ...testData.requestBody, flushFix: false }); - }); + it('should return full data without streaming with compression with flushFix', async () => { + await requestWithoutStreaming(testData.requestBody); + }); - it('should return full data without streaming without compression with flushFix', async () => { - await requestWithoutStreaming({ ...testData.requestBody, compressResponse: false }); - }); + it('should return full data without streaming with compression without flushFix', async () => { + await requestWithoutStreaming({ ...testData.requestBody, flushFix: false }); + }); - it('should return full data without streaming without compression without flushFix', async () => { - await requestWithoutStreaming({ - ...testData.requestBody, - compressResponse: false, - flushFix: false, + it('should return full data without streaming without compression with flushFix', async () => { + await requestWithoutStreaming({ ...testData.requestBody, compressResponse: false }); }); - }); - async function requestWithStreaming(body: AiopsApiLogRateAnalysis['body']) { - const resp = await fetch(`${kibanaServerUrl}/internal/aiops/log_rate_analysis`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - [ELASTIC_HTTP_VERSION_HEADER]: '1', - 'kbn-xsrf': 'stream', - }, - body: JSON.stringify(body), + it('should return full data without streaming without compression without flushFix', async () => { + await requestWithoutStreaming({ + ...testData.requestBody, + compressResponse: false, + flushFix: false, + }); }); - // compression is on by default so if the request body is undefined - // the response header should include "gzip" and otherwise be "null" - if (body.compressResponse === undefined) { - expect(resp.headers.get('content-encoding')).to.be('gzip'); - } else if (body.compressResponse === false) { - expect(resp.headers.get('content-encoding')).to.be(null); - } + async function requestWithStreaming(body: AiopsLogRateAnalysisSchema) { + const resp = await fetch(`${kibanaServerUrl}/internal/aiops/log_rate_analysis`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + [ELASTIC_HTTP_VERSION_HEADER]: apiVersion, + 'kbn-xsrf': 'stream', + }, + body: JSON.stringify(body), + }); + + // compression is on by default so if the request body is undefined + // the response header should include "gzip" and otherwise be "null" + if (body.compressResponse === undefined) { + expect(resp.headers.get('content-encoding')).to.be('gzip'); + } else if (body.compressResponse === false) { + expect(resp.headers.get('content-encoding')).to.be(null); + } - expect(resp.ok).to.be(true); - expect(resp.status).to.be(200); + expect(resp.ok).to.be(true); + expect(resp.status).to.be(200); - const stream = resp.body; + const stream = resp.body; - expect(stream).not.to.be(null); + expect(stream).not.to.be(null); - if (stream !== null) { - const data: any[] = []; - let chunkCounter = 0; - const parseStreamCallback = (c: number) => (chunkCounter = c); + if (stream !== null) { + const data: any[] = []; + let chunkCounter = 0; + const parseStreamCallback = (c: number) => (chunkCounter = c); - for await (const action of parseStream(stream, parseStreamCallback)) { - expect(action.type).not.to.be('error'); - data.push(action); - } + for await (const action of parseStream(stream, parseStreamCallback)) { + expect(action.type).not.to.be('error'); + data.push(action); + } - // Originally we assumed that we can assert streaming in contrast - // to non-streaming if there is more than one chunk. However, - // this turned out to be flaky since a stream could finish fast - // enough to contain only one chunk. So now we are checking if - // there's just one chunk or more. - expect(chunkCounter).to.be.greaterThan( - 0, - `Expected 'chunkCounter' to be greater than 0, got ${chunkCounter}.` - ); + // Originally we assumed that we can assert streaming in contrast + // to non-streaming if there is more than one chunk. However, + // this turned out to be flaky since a stream could finish fast + // enough to contain only one chunk. So now we are checking if + // there's just one chunk or more. + expect(chunkCounter).to.be.greaterThan( + 0, + `Expected 'chunkCounter' to be greater than 0, got ${chunkCounter}.` + ); - await assertAnalysisResult(data); + await assertAnalysisResult(data); + } } - } - it('should return data in chunks with streaming with compression with flushFix', async () => { - await requestWithStreaming(testData.requestBody); - }); + it('should return data in chunks with streaming with compression with flushFix', async () => { + await requestWithStreaming(testData.requestBody); + }); - it('should return data in chunks with streaming with compression without flushFix', async () => { - await requestWithStreaming({ ...testData.requestBody, flushFix: false }); - }); + it('should return data in chunks with streaming with compression without flushFix', async () => { + await requestWithStreaming({ ...testData.requestBody, flushFix: false }); + }); - it('should return data in chunks with streaming without compression with flushFix', async () => { - await requestWithStreaming({ ...testData.requestBody, compressResponse: false }); - }); + it('should return data in chunks with streaming without compression with flushFix', async () => { + await requestWithStreaming({ ...testData.requestBody, compressResponse: false }); + }); - it('should return data in chunks with streaming without compression without flushFix', async () => { - await requestWithStreaming({ - ...testData.requestBody, - compressResponse: false, - flushFix: false, + it('should return data in chunks with streaming without compression without flushFix', async () => { + await requestWithStreaming({ + ...testData.requestBody, + compressResponse: false, + flushFix: false, + }); }); }); }); diff --git a/x-pack/test/api_integration/apis/aiops/log_rate_analysis_groups_only.ts b/x-pack/test/api_integration/apis/aiops/log_rate_analysis_groups_only.ts index 8aeccc6af9a97f..fcc4bafabdf3c6 100644 --- a/x-pack/test/api_integration/apis/aiops/log_rate_analysis_groups_only.ts +++ b/x-pack/test/api_integration/apis/aiops/log_rate_analysis_groups_only.ts @@ -10,13 +10,20 @@ import fetch from 'node-fetch'; import { format as formatUrl } from 'url'; import expect from '@kbn/expect'; -import type { AiopsApiLogRateAnalysis } from '@kbn/aiops-plugin/common/api'; +import type { AiopsLogRateAnalysisSchema } from '@kbn/aiops-plugin/common/api/log_rate_analysis/schema'; +import type { AiopsLogRateAnalysisSchemaSignificantItem } from '@kbn/aiops-plugin/common/api/log_rate_analysis/schema_v2'; import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common'; import type { FtrProviderContext } from '../../ftr_provider_context'; import { parseStream } from './parse_stream'; -import { logRateAnalysisTestData } from './test_data'; +import { getLogRateAnalysisTestData, API_VERSIONS } from './test_data'; +import { + getAddSignificationItemsActions, + getHistogramActions, + getGroupActions, + getGroupHistogramActions, +} from './test_helpers'; export default ({ getService }: FtrProviderContext) => { const aiops = getService('aiops'); @@ -25,211 +32,229 @@ export default ({ getService }: FtrProviderContext) => { const kibanaServerUrl = formatUrl(config.get('servers.kibana')); const esArchiver = getService('esArchiver'); - // FLAKY: https://github.com/elastic/kibana/issues/169325 - describe.skip('POST /internal/aiops/log_rate_analysis - groups only', () => { - logRateAnalysisTestData.forEach((testData) => { - const overrides = { - loaded: 0, - remainingFieldCandidates: [], - significantTerms: testData.expected.significantTerms, - regroupOnly: true, - }; - - describe(`with ${testData.testName}`, () => { - before(async () => { - if (testData.esArchive) { - await esArchiver.loadIfNeeded(testData.esArchive); - } else if (testData.dataGenerator) { - await aiops.logRateAnalysisDataGenerator.generateData(testData.dataGenerator); - } - }); + describe('POST /internal/aiops/log_rate_analysis - groups only', () => { + API_VERSIONS.forEach((apiVersion) => { + getLogRateAnalysisTestData().forEach((testData) => { + let overrides: AiopsLogRateAnalysisSchema['overrides'] = {}; + + if (apiVersion === '1') { + overrides = { + loaded: 0, + remainingFieldCandidates: [], + significantTerms: testData.expected.significantItems, + regroupOnly: true, + } as AiopsLogRateAnalysisSchema['overrides']; + } - after(async () => { - if (testData.esArchive) { - await esArchiver.unload(testData.esArchive); - } else if (testData.dataGenerator) { - await aiops.logRateAnalysisDataGenerator.removeGeneratedData(testData.dataGenerator); - } - }); + if (apiVersion === '2') { + overrides = { + loaded: 0, + remainingFieldCandidates: [], + significantItems: testData.expected + .significantItems as AiopsLogRateAnalysisSchemaSignificantItem[], + regroupOnly: true, + } as AiopsLogRateAnalysisSchema['overrides']; + } - async function assertAnalysisResult(data: any[]) { - expect(data.length).to.eql( - testData.expected.actionsLengthGroupOnly, - `Expected 'actionsLengthGroupOnly' to be ${testData.expected.actionsLengthGroupOnly}, got ${data.length}.` - ); - data.forEach((d) => { - expect(typeof d.type).to.be('string'); + describe(`with v${apiVersion} - ${testData.testName}`, () => { + before(async () => { + if (testData.esArchive) { + await esArchiver.loadIfNeeded(testData.esArchive); + } else if (testData.dataGenerator) { + await aiops.logRateAnalysisDataGenerator.generateData(testData.dataGenerator); + } }); - const addSignificantTermsActions = data.filter( - (d) => d.type === testData.expected.significantTermFilter - ); - expect(addSignificantTermsActions.length).to.eql( - 0, - `Expected significant terms actions to be 0, got ${addSignificantTermsActions.length}` - ); - - const histogramActions = data.filter((d) => d.type === testData.expected.histogramFilter); - // for each significant term we should get a histogram - expect(histogramActions.length).to.eql( - 0, - `Expected histogram actions to be 0, got ${histogramActions.length}` - ); - - const groupActions = data.filter((d) => d.type === testData.expected.groupFilter); - const groups = groupActions.flatMap((d) => d.payload); - - expect(orderBy(groups, ['docCount'], ['desc'])).to.eql( - orderBy(testData.expected.groups, ['docCount'], ['desc']), - 'Grouping result does not match expected values.' - ); - - const groupHistogramActions = data.filter( - (d) => d.type === testData.expected.groupHistogramFilter - ); - const groupHistograms = groupHistogramActions.flatMap((d) => d.payload); - // for each significant terms group we should get a histogram - expect(groupHistograms.length).to.be(groups.length); - // each histogram should have a length of 20 items. - groupHistograms.forEach((h, index) => { - expect(h.histogram.length).to.be(20); + after(async () => { + if (testData.esArchive) { + await esArchiver.unload(testData.esArchive); + } else if (testData.dataGenerator) { + await aiops.logRateAnalysisDataGenerator.removeGeneratedData(testData.dataGenerator); + } }); - } - async function requestWithoutStreaming(body: AiopsApiLogRateAnalysis['body']) { - const resp = await supertest - .post(`/internal/aiops/log_rate_analysis`) - .set('kbn-xsrf', 'kibana') - .set(ELASTIC_HTTP_VERSION_HEADER, '1') - .send(body) - .expect(200); - - // compression is on by default so if the request body is undefined - // the response header should include "gzip" and otherwise be "undefined" - if (body.compressResponse === undefined) { - expect(resp.header['content-encoding']).to.be('gzip'); - } else if (body.compressResponse === false) { - expect(resp.header['content-encoding']).to.be(undefined); + async function assertAnalysisResult(data: any[]) { + expect(data.length).to.eql( + testData.expected.actionsLengthGroupOnly, + `Expected 'actionsLengthGroupOnly' to be ${testData.expected.actionsLengthGroupOnly}, got ${data.length}.` + ); + data.forEach((d) => { + expect(typeof d.type).to.be('string'); + }); + + const addSignificantItemsActions = getAddSignificationItemsActions(data, apiVersion); + expect(addSignificantItemsActions.length).to.eql( + 0, + `Expected significant items actions to be 0, got ${addSignificantItemsActions.length}` + ); + + const histogramActions = getHistogramActions(data, apiVersion); + + // for each significant item we should get a histogram + expect(histogramActions.length).to.eql( + 0, + `Expected histogram actions to be 0, got ${histogramActions.length}` + ); + + const groupActions = getGroupActions(data, apiVersion); + const groups = groupActions.flatMap((d) => d.payload); + + expect(orderBy(groups, ['docCount'], ['desc'])).to.eql( + orderBy(testData.expected.groups, ['docCount'], ['desc']), + 'Grouping result does not match expected values.' + ); + + const groupHistogramActions = getGroupHistogramActions(data, apiVersion); + const groupHistograms = groupHistogramActions.flatMap((d) => d.payload); + // for each significant items group we should get a histogram + expect(groupHistograms.length).to.be(groups.length); + // each histogram should have a length of 20 items. + groupHistograms.forEach((h, index) => { + expect(h.histogram.length).to.be(20); + }); } - expect(Buffer.isBuffer(resp.body)).to.be(true); + async function requestWithoutStreaming( + body: AiopsLogRateAnalysisSchema + ) { + const resp = await supertest + .post(`/internal/aiops/log_rate_analysis`) + .set('kbn-xsrf', 'kibana') + .set(ELASTIC_HTTP_VERSION_HEADER, apiVersion) + .send(body) + .expect(200); + + // compression is on by default so if the request body is undefined + // the response header should include "gzip" and otherwise be "undefined" + if (body.compressResponse === undefined) { + expect(resp.header['content-encoding']).to.be('gzip'); + } else if (body.compressResponse === false) { + expect(resp.header['content-encoding']).to.be(undefined); + } - const chunks: string[] = resp.body.toString().split('\n'); + expect(Buffer.isBuffer(resp.body)).to.be(true); - expect(chunks.length).to.eql( - testData.expected.chunksLengthGroupOnly, - `Expected 'chunksLength' to be ${testData.expected.chunksLengthGroupOnly}, got ${chunks.length}.` - ); + const chunks: string[] = resp.body.toString().split('\n'); - const lastChunk = chunks.pop(); - expect(lastChunk).to.be(''); + expect(chunks.length).to.eql( + testData.expected.chunksLengthGroupOnly, + `Expected 'chunksLength' to be ${testData.expected.chunksLengthGroupOnly}, got ${chunks.length}.` + ); - let data: any[] = []; + const lastChunk = chunks.pop(); + expect(lastChunk).to.be(''); - expect(() => { - data = chunks.map((c) => JSON.parse(c)); - }).not.to.throwError(); + let data: any[] = []; - await assertAnalysisResult(data); - } + expect(() => { + data = chunks.map((c) => JSON.parse(c)); + }).not.to.throwError(); - it('should return group only data without streaming with compression with flushFix', async () => { - await requestWithoutStreaming({ ...testData.requestBody, overrides }); - }); + await assertAnalysisResult(data); + } - it('should return group only data without streaming with compression without flushFix', async () => { - await requestWithoutStreaming({ ...testData.requestBody, overrides, flushFix: false }); - }); + it('should return group only data without streaming with compression with flushFix', async () => { + await requestWithoutStreaming({ ...testData.requestBody, overrides }); + }); - it('should return group only data without streaming without compression with flushFix', async () => { - await requestWithoutStreaming({ - ...testData.requestBody, - overrides, - compressResponse: false, + it('should return group only data without streaming with compression without flushFix', async () => { + await requestWithoutStreaming({ + ...testData.requestBody, + overrides, + flushFix: false, + }); }); - }); - it('should return group only data without streaming without compression without flushFix', async () => { - await requestWithoutStreaming({ - ...testData.requestBody, - overrides, - compressResponse: false, - flushFix: false, + it('should return group only data without streaming without compression with flushFix', async () => { + await requestWithoutStreaming({ + ...testData.requestBody, + overrides, + compressResponse: false, + }); }); - }); - async function requestWithStreaming(body: AiopsApiLogRateAnalysis['body']) { - const resp = await fetch(`${kibanaServerUrl}/internal/aiops/log_rate_analysis`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - [ELASTIC_HTTP_VERSION_HEADER]: '1', - 'kbn-xsrf': 'stream', - }, - body: JSON.stringify(body), + it('should return group only data without streaming without compression without flushFix', async () => { + await requestWithoutStreaming({ + ...testData.requestBody, + overrides, + compressResponse: false, + flushFix: false, + }); }); - // compression is on by default so if the request body is undefined - // the response header should include "gzip" and otherwise be "null" - if (body.compressResponse === undefined) { - expect(resp.headers.get('content-encoding')).to.be('gzip'); - } else if (body.compressResponse === false) { - expect(resp.headers.get('content-encoding')).to.be(null); - } + async function requestWithStreaming(body: AiopsLogRateAnalysisSchema) { + const resp = await fetch(`${kibanaServerUrl}/internal/aiops/log_rate_analysis`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + [ELASTIC_HTTP_VERSION_HEADER]: apiVersion, + 'kbn-xsrf': 'stream', + }, + body: JSON.stringify(body), + }); + + // compression is on by default so if the request body is undefined + // the response header should include "gzip" and otherwise be "null" + if (body.compressResponse === undefined) { + expect(resp.headers.get('content-encoding')).to.be('gzip'); + } else if (body.compressResponse === false) { + expect(resp.headers.get('content-encoding')).to.be(null); + } - expect(resp.ok).to.be(true); - expect(resp.status).to.be(200); + expect(resp.ok).to.be(true); + expect(resp.status).to.be(200); - const stream = resp.body; + const stream = resp.body; - expect(stream).not.to.be(null); + expect(stream).not.to.be(null); - if (stream !== null) { - const data: any[] = []; - let chunkCounter = 0; - const parseStreamCallback = (c: number) => (chunkCounter = c); + if (stream !== null) { + const data: any[] = []; + let chunkCounter = 0; + const parseStreamCallback = (c: number) => (chunkCounter = c); - for await (const action of parseStream(stream, parseStreamCallback)) { - expect(action.type).not.to.be('error'); - data.push(action); - } + for await (const action of parseStream(stream, parseStreamCallback)) { + expect(action.type).not.to.be('error'); + data.push(action); + } - // Originally we assumed that we can assert streaming in contrast - // to non-streaming if there is more than one chunk. However, - // this turned out to be flaky since a stream could finish fast - // enough to contain only one chunk. So now we are checking if - // there's just one chunk or more. - expect(chunkCounter).to.be.greaterThan( - 0, - `Expected 'chunkCounter' to be greater than 0, got ${chunkCounter}.` - ); + // Originally we assumed that we can assert streaming in contrast + // to non-streaming if there is more than one chunk. However, + // this turned out to be flaky since a stream could finish fast + // enough to contain only one chunk. So now we are checking if + // there's just one chunk or more. + expect(chunkCounter).to.be.greaterThan( + 0, + `Expected 'chunkCounter' to be greater than 0, got ${chunkCounter}.` + ); - await assertAnalysisResult(data); + await assertAnalysisResult(data); + } } - } - it('should return group only in chunks with streaming with compression with flushFix', async () => { - await requestWithStreaming({ ...testData.requestBody, overrides }); - }); + it('should return group only in chunks with streaming with compression with flushFix', async () => { + await requestWithStreaming({ ...testData.requestBody, overrides }); + }); - it('should return group only in chunks with streaming with compression without flushFix', async () => { - await requestWithStreaming({ ...testData.requestBody, overrides, flushFix: false }); - }); + it('should return group only in chunks with streaming with compression without flushFix', async () => { + await requestWithStreaming({ ...testData.requestBody, overrides, flushFix: false }); + }); - it('should return group only in chunks with streaming without compression with flushFix', async () => { - await requestWithStreaming({ - ...testData.requestBody, - overrides, - compressResponse: false, + it('should return group only in chunks with streaming without compression with flushFix', async () => { + await requestWithStreaming({ + ...testData.requestBody, + overrides, + compressResponse: false, + }); }); - }); - it('should return group only in chunks with streaming without compression without flushFix', async () => { - await requestWithStreaming({ - ...testData.requestBody, - overrides, - compressResponse: false, - flushFix: false, + it('should return group only in chunks with streaming without compression without flushFix', async () => { + await requestWithStreaming({ + ...testData.requestBody, + overrides, + compressResponse: false, + flushFix: false, + }); }); }); }); diff --git a/x-pack/test/api_integration/apis/aiops/log_rate_analysis_no_index.ts b/x-pack/test/api_integration/apis/aiops/log_rate_analysis_no_index.ts index 088db66e082bda..118cae10c0c9d1 100644 --- a/x-pack/test/api_integration/apis/aiops/log_rate_analysis_no_index.ts +++ b/x-pack/test/api_integration/apis/aiops/log_rate_analysis_no_index.ts @@ -10,53 +10,56 @@ import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common'; import type { FtrProviderContext } from '../../ftr_provider_context'; -import { logRateAnalysisTestData } from './test_data'; +import { getLogRateAnalysisTestData, API_VERSIONS } from './test_data'; +import { getErrorActions } from './test_helpers'; export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); describe('POST /internal/aiops/log_rate_analysis - no index', () => { - logRateAnalysisTestData.forEach((testData) => { - describe(`with ${testData.testName}`, () => { - it('should return an error for non existing index without streaming', async () => { - const resp = await supertest - .post(`/internal/aiops/log_rate_analysis`) - .set('kbn-xsrf', 'kibana') - .set(ELASTIC_HTTP_VERSION_HEADER, '1') - .send({ - ...testData.requestBody, - index: 'does_not_exist', - }) - .expect(200); + API_VERSIONS.forEach((apiVersion) => { + getLogRateAnalysisTestData().forEach((testData) => { + describe(`with v${apiVersion} - ${testData.testName}`, () => { + it('should return an error for non existing index without streaming', async () => { + const resp = await supertest + .post(`/internal/aiops/log_rate_analysis`) + .set('kbn-xsrf', 'kibana') + .set(ELASTIC_HTTP_VERSION_HEADER, apiVersion) + .send({ + ...testData.requestBody, + index: 'does_not_exist', + }) + .expect(200); - const chunks: string[] = resp.body.toString().split('\n'); + const chunks: string[] = resp.body.toString().split('\n'); - expect(chunks.length).to.eql( - testData.expected.noIndexChunksLength, - `Expected 'noIndexChunksLength' to be ${testData.expected.noIndexChunksLength}, got ${chunks.length}.` - ); + expect(chunks.length).to.eql( + testData.expected.noIndexChunksLength, + `Expected 'noIndexChunksLength' to be ${testData.expected.noIndexChunksLength}, got ${chunks.length}.` + ); - const lastChunk = chunks.pop(); - expect(lastChunk).to.be(''); + const lastChunk = chunks.pop(); + expect(lastChunk).to.be(''); - let data: any[] = []; + let data: any[] = []; - expect(() => { - data = chunks.map((c) => JSON.parse(c)); - }).not.to.throwError(); + expect(() => { + data = chunks.map((c) => JSON.parse(c)); + }).not.to.throwError(); - expect(data.length).to.eql( - testData.expected.noIndexActionsLength, - `Expected 'noIndexActionsLength' to be ${testData.expected.noIndexActionsLength}, got ${data.length}.` - ); - data.forEach((d) => { - expect(typeof d.type).to.be('string'); - }); + expect(data.length).to.eql( + testData.expected.noIndexActionsLength, + `Expected 'noIndexActionsLength' to be ${testData.expected.noIndexActionsLength}, got ${data.length}.` + ); + data.forEach((d) => { + expect(typeof d.type).to.be('string'); + }); - const errorActions = data.filter((d) => d.type === testData.expected.errorFilter); - expect(errorActions.length).to.be(1); + const errorActions = getErrorActions(data); + expect(errorActions.length).to.be(1); - expect(errorActions[0].payload).to.be('Failed to fetch index information.'); + expect(errorActions[0].payload).to.be('Failed to fetch index information.'); + }); }); }); }); diff --git a/x-pack/test/api_integration/apis/aiops/test_data.ts b/x-pack/test/api_integration/apis/aiops/test_data.ts index 184925310940e7..291779ed6c7b24 100644 --- a/x-pack/test/api_integration/apis/aiops/test_data.ts +++ b/x-pack/test/api_integration/apis/aiops/test_data.ts @@ -10,12 +10,19 @@ // that also the jest unit tests use mocks that are not outdated. import { significantTerms as artificialLogSignificantTerms } from '@kbn/aiops-plugin/common/__mocks__/artificial_logs/significant_terms'; import { significantLogPatterns as artificialLogSignificantLogPatterns } from '@kbn/aiops-plugin/common/__mocks__/artificial_logs/significant_log_patterns'; -import { finalSignificantTermGroups as artificialLogsSignificantTermGroups } from '@kbn/aiops-plugin/common/__mocks__/artificial_logs/final_significant_term_groups'; -import { finalSignificantTermGroupsTextfield as artificialLogsSignificantTermGroupsTextfield } from '@kbn/aiops-plugin/common/__mocks__/artificial_logs/final_significant_term_groups_textfield'; +import { finalSignificantItemGroups as artificialLogsSignificantItemGroups } from '@kbn/aiops-plugin/common/__mocks__/artificial_logs/final_significant_item_groups'; +import { finalSignificantItemGroupsTextfield as artificialLogsSignificantItemGroupsTextfield } from '@kbn/aiops-plugin/common/__mocks__/artificial_logs/final_significant_item_groups_textfield'; + +import type { + AiopsLogRateAnalysisSchema, + AiopsLogRateAnalysisApiVersion as ApiVersion, +} from '@kbn/aiops-plugin/common/api/log_rate_analysis/schema'; import type { TestData } from './types'; -export const logRateAnalysisTestData: TestData[] = [ +export const API_VERSIONS: ApiVersion[] = ['1', '2']; + +export const getLogRateAnalysisTestData = (): Array> => [ { testName: 'ecommerce', esArchive: 'x-pack/test/functional/es_archives/ml/ecommerce', @@ -30,7 +37,7 @@ export const logRateAnalysisTestData: TestData[] = [ start: 0, timeFieldName: 'order_date', grouping: true, - }, + } as AiopsLogRateAnalysisSchema, expected: { chunksLength: 35, chunksLengthGroupOnly: 5, @@ -38,12 +45,7 @@ export const logRateAnalysisTestData: TestData[] = [ actionsLengthGroupOnly: 4, noIndexChunksLength: 4, noIndexActionsLength: 3, - significantTermFilter: 'add_significant_terms', - groupFilter: 'add_significant_terms_group', - groupHistogramFilter: 'add_significant_terms_group_histogram', - histogramFilter: 'add_significant_terms_histogram', - errorFilter: 'add_error', - significantTerms: [ + significantItems: [ { key: 'day_of_week:Thursday', type: 'keyword', @@ -89,7 +91,7 @@ export const logRateAnalysisTestData: TestData[] = [ deviationMin: 1668855600000, deviationMax: 1668924000000, grouping: true, - }, + } as AiopsLogRateAnalysisSchema, expected: { chunksLength: 27, chunksLengthGroupOnly: 11, @@ -97,13 +99,8 @@ export const logRateAnalysisTestData: TestData[] = [ actionsLengthGroupOnly: 10, noIndexChunksLength: 4, noIndexActionsLength: 3, - significantTermFilter: 'add_significant_terms', - groupFilter: 'add_significant_terms_group', - groupHistogramFilter: 'add_significant_terms_group_histogram', - histogramFilter: 'add_significant_terms_histogram', - errorFilter: 'add_error', - significantTerms: artificialLogSignificantTerms, - groups: artificialLogsSignificantTermGroups, + significantItems: artificialLogSignificantTerms, + groups: artificialLogsSignificantItemGroups, histogramLength: 20, }, }, @@ -121,7 +118,7 @@ export const logRateAnalysisTestData: TestData[] = [ deviationMin: 1668855600000, deviationMax: 1668924000000, grouping: true, - }, + } as AiopsLogRateAnalysisSchema, expected: { chunksLength: 30, chunksLengthGroupOnly: 11, @@ -129,13 +126,8 @@ export const logRateAnalysisTestData: TestData[] = [ actionsLengthGroupOnly: 10, noIndexChunksLength: 4, noIndexActionsLength: 3, - significantTermFilter: 'add_significant_terms', - groupFilter: 'add_significant_terms_group', - groupHistogramFilter: 'add_significant_terms_group_histogram', - histogramFilter: 'add_significant_terms_histogram', - errorFilter: 'add_error', - significantTerms: [...artificialLogSignificantTerms, ...artificialLogSignificantLogPatterns], - groups: artificialLogsSignificantTermGroupsTextfield, + significantItems: [...artificialLogSignificantTerms, ...artificialLogSignificantLogPatterns], + groups: artificialLogsSignificantItemGroupsTextfield, histogramLength: 20, }, }, @@ -153,7 +145,7 @@ export const logRateAnalysisTestData: TestData[] = [ deviationMin: 1668769200000, deviationMax: 1668837600000, grouping: true, - }, + } as AiopsLogRateAnalysisSchema, expected: { chunksLength: 27, chunksLengthGroupOnly: 11, @@ -161,13 +153,8 @@ export const logRateAnalysisTestData: TestData[] = [ actionsLengthGroupOnly: 10, noIndexChunksLength: 4, noIndexActionsLength: 3, - significantTermFilter: 'add_significant_terms', - groupFilter: 'add_significant_terms_group', - groupHistogramFilter: 'add_significant_terms_group_histogram', - histogramFilter: 'add_significant_terms_histogram', - errorFilter: 'add_error', - significantTerms: artificialLogSignificantTerms, - groups: artificialLogsSignificantTermGroups, + significantItems: artificialLogSignificantTerms, + groups: artificialLogsSignificantItemGroups, histogramLength: 20, }, }, @@ -185,7 +172,7 @@ export const logRateAnalysisTestData: TestData[] = [ deviationMin: 1668769200000, deviationMax: 1668837600000, grouping: true, - }, + } as AiopsLogRateAnalysisSchema, expected: { chunksLength: 30, chunksLengthGroupOnly: 11, @@ -193,13 +180,8 @@ export const logRateAnalysisTestData: TestData[] = [ actionsLengthGroupOnly: 10, noIndexChunksLength: 4, noIndexActionsLength: 3, - significantTermFilter: 'add_significant_terms', - groupFilter: 'add_significant_terms_group', - groupHistogramFilter: 'add_significant_terms_group_histogram', - histogramFilter: 'add_significant_terms_histogram', - errorFilter: 'add_error', - significantTerms: [...artificialLogSignificantTerms, ...artificialLogSignificantLogPatterns], - groups: artificialLogsSignificantTermGroupsTextfield, + significantItems: [...artificialLogSignificantTerms, ...artificialLogSignificantLogPatterns], + groups: artificialLogsSignificantItemGroupsTextfield, histogramLength: 20, }, }, diff --git a/x-pack/test/api_integration/apis/aiops/test_helpers.ts b/x-pack/test/api_integration/apis/aiops/test_helpers.ts new file mode 100644 index 00000000000000..3818b96b9bbe16 --- /dev/null +++ b/x-pack/test/api_integration/apis/aiops/test_helpers.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { AiopsLogRateAnalysisApiVersion as ApiVersion } from '@kbn/aiops-plugin/common/api/log_rate_analysis/schema'; + +export const getAddSignificationItemsActions = (data: any[], apiVersion: ApiVersion) => + data.filter( + (d) => d.type === (apiVersion === '1' ? 'add_significant_terms' : 'add_significant_items') + ); + +export const getHistogramActions = (data: any[], apiVersion: ApiVersion) => + data.filter( + (d) => + d.type === + (apiVersion === '1' ? 'add_significant_terms_histogram' : 'add_significant_items_histogram') + ); + +export const getGroupActions = (data: any[], apiVersion: ApiVersion) => + data.filter( + (d) => + d.type === + (apiVersion === '1' ? 'add_significant_terms_group' : 'add_significant_items_group') + ); + +export const getGroupHistogramActions = (data: any[], apiVersion: ApiVersion) => + data.filter( + (d) => + d.type === + (apiVersion === '1' + ? 'add_significant_terms_group_histogram' + : 'add_significant_items_group_histogram') + ); + +export const getErrorActions = (data: any[]) => data.filter((d) => d.type === 'add_error'); diff --git a/x-pack/test/api_integration/apis/aiops/types.ts b/x-pack/test/api_integration/apis/aiops/types.ts index c4e9eb81911082..df38825d9698d5 100644 --- a/x-pack/test/api_integration/apis/aiops/types.ts +++ b/x-pack/test/api_integration/apis/aiops/types.ts @@ -5,16 +5,19 @@ * 2.0. */ -import type { AiopsApiLogRateAnalysis } from '@kbn/aiops-plugin/common/api'; -import type { SignificantTerm, SignificantTermGroup } from '@kbn/ml-agg-utils'; +import type { + AiopsLogRateAnalysisSchema, + AiopsLogRateAnalysisApiVersion as ApiVersion, +} from '@kbn/aiops-plugin/common/api/log_rate_analysis/schema'; +import type { SignificantItem, SignificantItemGroup } from '@kbn/ml-agg-utils'; import type { LogRateAnalysisDataGenerator } from '../../../functional/services/aiops/log_rate_analysis_data_generator'; -export interface TestData { +export interface TestData { testName: string; esArchive?: string; dataGenerator?: LogRateAnalysisDataGenerator; - requestBody: AiopsApiLogRateAnalysis['body']; + requestBody: AiopsLogRateAnalysisSchema; expected: { chunksLength: number; chunksLengthGroupOnly: number; @@ -22,13 +25,8 @@ export interface TestData { actionsLengthGroupOnly: number; noIndexChunksLength: number; noIndexActionsLength: number; - significantTermFilter: 'add_significant_terms'; - groupFilter: 'add_significant_terms_group'; - groupHistogramFilter: 'add_significant_terms_group_histogram'; - histogramFilter: 'add_significant_terms_histogram'; - errorFilter: 'add_error'; - significantTerms: SignificantTerm[]; - groups: SignificantTermGroup[]; + significantItems: SignificantItem[]; + groups: SignificantItemGroup[]; histogramLength: number; }; } 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 0065057d762f40..01e7ae46ce8fe3 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 @@ -26,7 +26,8 @@ export default function ({ getService }: FtrProviderContext) { createDatastream, } = initElasticsearchHelpers(getService); - describe('Component templates', function () { + // FLAKY: https://github.com/elastic/kibana/issues/170999 + describe.skip('Component templates', function () { after(async () => { await cleanUpIndexTemplates(); await cleanUpComponentTemplates(); @@ -146,6 +147,10 @@ export default function ({ getService }: FtrProviderContext) { }, }, }, + lifecycle: { + enabled: true, + data_retention: '2d', + }, }, _meta: { description: 'set number of shards to one', diff --git a/x-pack/test/api_integration/apis/management/index_management/templates.js b/x-pack/test/api_integration/apis/management/index_management/templates.js index 1cb58c0957e170..bb4007852b0379 100644 --- a/x-pack/test/api_integration/apis/management/index_management/templates.js +++ b/x-pack/test/api_integration/apis/management/index_management/templates.js @@ -25,13 +25,31 @@ export default function ({ getService }) { cleanUpTemplates, } = registerHelpers({ supertest }); - describe('index templates', () => { + // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/170980 + describe.skip('index templates', () => { after(() => Promise.all([cleanUpEsResources(), cleanUpTemplates()])); describe('get all', () => { - const templateName = `template-${getRandomString()}`; - const indexTemplate = getTemplatePayload(templateName, [getRandomString()]); - const legacyTemplate = getTemplatePayload(templateName, [getRandomString()], true); + const indexTemplate = getTemplatePayload(`template-${getRandomString()}`, [ + getRandomString(), + ]); + const legacyTemplate = getTemplatePayload( + `template-${getRandomString()}`, + [getRandomString()], + true + ); + const tmpTemplate = getTemplatePayload(`template-${getRandomString()}`, [getRandomString()]); + const indexTemplateWithLifecycle = { + ...tmpTemplate, + dataStream: {}, + template: { + ...tmpTemplate.template, + lifecycle: { + enabled: true, + data_retention: '10d', + }, + }, + }; beforeEach(async () => { const res1 = await createTemplate(indexTemplate); @@ -43,6 +61,11 @@ export default function ({ getService }) { if (res2.status !== 200) { throw new Error(res2.body.message); } + + const res3 = await createTemplate(indexTemplateWithLifecycle); + if (res3.status !== 200) { + throw new Error(res3.body.message); + } }); it('should list all the index templates with the expected parameters', async () => { @@ -106,6 +129,36 @@ export default function ({ getService }) { ].sort(); expect(Object.keys(legacyTemplateFound).sort()).to.eql(expectedLegacyKeys); + + // Index template with lifecycle + const templateWithLifecycle = allTemplates.templates.find( + (template) => template.name === indexTemplateWithLifecycle.name + ); + + if (!templateWithLifecycle) { + throw new Error( + `Index template with lifecycle "${ + indexTemplateWithLifecycle.name + }" not found in ${JSON.stringify(allTemplates.templates, null, 2)}` + ); + } + + const expectedWithLifecycleKeys = [ + 'name', + 'indexPatterns', + 'lifecycle', + 'hasSettings', + 'hasAliases', + 'hasMappings', + 'ilmPolicy', + 'priority', + 'composedOf', + 'dataStream', + 'version', + '_kbnMeta', + ].sort(); + + expect(Object.keys(templateWithLifecycle).sort()).to.eql(expectedWithLifecycleKeys); }); }); diff --git a/x-pack/test/api_integration/apis/metrics_ui/constants.ts b/x-pack/test/api_integration/apis/metrics_ui/constants.ts index 7fd25f09875b4f..13fd1acf7b1997 100644 --- a/x-pack/test/api_integration/apis/metrics_ui/constants.ts +++ b/x-pack/test/api_integration/apis/metrics_ui/constants.ts @@ -40,7 +40,7 @@ export const DATES = { }, logs_and_metrics_with_aws: { min: 1564083185000, - max: 1564083493080, + max: 1564083705100, }, }, 'alert-test-data': { diff --git a/x-pack/test/api_integration/apis/search/search.ts b/x-pack/test/api_integration/apis/search/search.ts index 391923601d7c59..15c774eef34ef9 100644 --- a/x-pack/test/api_integration/apis/search/search.ts +++ b/x-pack/test/api_integration/apis/search/search.ts @@ -186,6 +186,71 @@ export default function ({ getService }: FtrProviderContext) { expect(resp2.body.isRunning).to.be(true); }); + it('should cancel an async search without server crash', async function () { + await markRequiresShardDelayAgg(this); + + const resp = await supertest + .post(`/internal/search/ese`) + .set(ELASTIC_HTTP_VERSION_HEADER, '1') + .set('kbn-xsrf', 'foo') + .send({ + params: { + body: { + query: { + match_all: {}, + }, + ...shardDelayAgg('10s'), + }, + wait_for_completion_timeout: '1ms', + }, + }) + .expect(200); + + const { id } = resp.body; + expect(id).not.to.be(undefined); + expect(resp.body.isPartial).to.be(true); + expect(resp.body.isRunning).to.be(true); + + // Send a follow-up request that waits up to 10s for completion + const req = supertest + .post(`/internal/search/ese/${id}`) + .set(ELASTIC_HTTP_VERSION_HEADER, '1') + .set('kbn-xsrf', 'foo') + .send({ params: { wait_for_completion_timeout: '10s' } }) + .expect(200); + + // After 2s, abort and send the cancellation (to result in a race towards cancellation) + // This should be swallowed and not kill the Kibana server + await new Promise((resolve) => + setTimeout(() => { + req.abort(); + resolve(null); + }, 2000) + ); + await supertest + .delete(`/internal/search/ese/${id}`) + .set(ELASTIC_HTTP_VERSION_HEADER, '1') + .set('kbn-xsrf', 'foo') + .expect(200); + + let err: Error | undefined; + try { + await req; + } catch (e) { + err = e; + } + + expect(err).not.to.be(undefined); + + // Ensure the search was succesfully cancelled + await supertest + .post(`/internal/search/ese/${id}`) + .set(ELASTIC_HTTP_VERSION_HEADER, '1') + .set('kbn-xsrf', 'foo') + .send({}) + .expect(404); + }); + it('should fail without kbn-xref header', async () => { const resp = await supertest .post(`/internal/search/ese`) diff --git a/x-pack/test/api_integration/apis/synthetics/add_monitor_private_location.ts b/x-pack/test/api_integration/apis/synthetics/add_monitor_private_location.ts index 2b22519f6894df..73314ba770384e 100644 --- a/x-pack/test/api_integration/apis/synthetics/add_monitor_private_location.ts +++ b/x-pack/test/api_integration/apis/synthetics/add_monitor_private_location.ts @@ -398,6 +398,7 @@ export default function ({ getService }: FtrProviderContext) { id: monitorId, location: { id: testFleetPolicyID }, namespace: formatKibanaNamespace(SPACE_ID), + spaceId: SPACE_ID, }) ); await supertestWithoutAuth diff --git a/x-pack/test/api_integration/apis/synthetics/inspect_monitor.ts b/x-pack/test/api_integration/apis/synthetics/inspect_monitor.ts index 947347fb2d4e41..6bf81370365d50 100644 --- a/x-pack/test/api_integration/apis/synthetics/inspect_monitor.ts +++ b/x-pack/test/api_integration/apis/synthetics/inspect_monitor.ts @@ -84,7 +84,9 @@ export default function ({ getService }: FtrProviderContext) { 'response.include_body_max_bytes': '1024', ipv4: true, ipv6: true, - fields: {}, + fields: { + meta: { space_id: 'default' }, + }, fields_under_root: true, }, ], @@ -147,6 +149,7 @@ export default function ({ getService }: FtrProviderContext) { throttling: { download: 5, upload: 3, latency: 20 }, original_space: 'default', fields: { + meta: { space_id: 'default' }, 'monitor.project.name': 'test-project-cb47c83a-45e7-416a-9301-cb476b5bff01', 'monitor.project.id': 'test-project-cb47c83a-45e7-416a-9301-cb476b5bff01', }, @@ -223,6 +226,7 @@ export default function ({ getService }: FtrProviderContext) { add_fields: { target: '', fields: { + meta: { space_id: 'default' }, 'monitor.fleet_managed': true, }, }, diff --git a/x-pack/test/api_integration/apis/synthetics/sample_data/test_policy.ts b/x-pack/test/api_integration/apis/synthetics/sample_data/test_policy.ts index efbeca6161d56d..eec5ec78d87b31 100644 --- a/x-pack/test/api_integration/apis/synthetics/sample_data/test_policy.ts +++ b/x-pack/test/api_integration/apis/synthetics/sample_data/test_policy.ts @@ -22,6 +22,7 @@ interface PolicyProps { proxyUrl?: string; params?: Record; isBrowser?: boolean; + spaceId?: string; } export const getTestSyntheticsPolicy = (props: PolicyProps): PackagePolicy => { @@ -130,6 +131,8 @@ export const getHttpInput = ({ proxyUrl, isTLSEnabled, isBrowser, + spaceId, + namespace, name = 'check if title is present-Test private location 0', }: PolicyProps) => { const enabled = !isBrowser; @@ -197,6 +200,7 @@ export const getHttpInput = ({ fields: { 'monitor.fleet_managed': true, config_id: id, + meta: { space_id: spaceId ?? 'default' }, 'monitor.project.name': projectId, 'monitor.project.id': projectId, }, @@ -297,6 +301,9 @@ export const getHttpInput = ({ add_fields: { fields: { config_id: id, + meta: { + space_id: spaceId ?? 'default', + }, 'monitor.fleet_managed': true, ...(projectId ? { 'monitor.project.id': projectId, 'monitor.project.name': projectId } diff --git a/x-pack/test/api_integration/apis/synthetics/sample_data/test_project_monitor_policy.ts b/x-pack/test/api_integration/apis/synthetics/sample_data/test_project_monitor_policy.ts index 73171abe244357..4b9b9b97474222 100644 --- a/x-pack/test/api_integration/apis/synthetics/sample_data/test_project_monitor_policy.ts +++ b/x-pack/test/api_integration/apis/synthetics/sample_data/test_project_monitor_policy.ts @@ -158,6 +158,7 @@ export const getTestProjectSyntheticsPolicyLightweight = ( config_id: configId, 'monitor.project.name': projectId, 'monitor.project.id': projectId, + meta: { space_id: 'default' }, }, target: '', }, @@ -282,6 +283,7 @@ export const getTestProjectSyntheticsPolicyLightweight = ( 'monitor.fleet_managed': true, 'monitor.project.id': projectId, 'monitor.project.name': projectId, + meta: { space_id: 'default' }, }, target: '', }, diff --git a/x-pack/test/api_integration/apis/synthetics/services/private_location_test_service.ts b/x-pack/test/api_integration/apis/synthetics/services/private_location_test_service.ts index 02f4612cdb13e2..99ba316d3f8508 100644 --- a/x-pack/test/api_integration/apis/synthetics/services/private_location_test_service.ts +++ b/x-pack/test/api_integration/apis/synthetics/services/private_location_test_service.ts @@ -11,7 +11,7 @@ import { SyntheticsPrivateLocations } from '@kbn/synthetics-plugin/common/runtim import { FtrProviderContext } from '../../../ftr_provider_context'; import { KibanaSupertestProvider } from '../../../../../../test/api_integration/services/supertest'; -export const INSTALLED_VERSION = '1.1.0'; +export const INSTALLED_VERSION = '1.1.1'; export class PrivateLocationTestService { private supertest: ReturnType; diff --git a/x-pack/test/api_integration_basic/apis/aiops/permissions.ts b/x-pack/test/api_integration_basic/apis/aiops/permissions.ts index 3a42d1e99d1a63..a77d9a634418e4 100644 --- a/x-pack/test/api_integration_basic/apis/aiops/permissions.ts +++ b/x-pack/test/api_integration_basic/apis/aiops/permissions.ts @@ -11,50 +11,59 @@ import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common'; import expect from '@kbn/expect'; -import type { AiopsApiLogRateAnalysis } from '@kbn/aiops-plugin/common/api'; +import type { + AiopsLogRateAnalysisSchema, + AiopsLogRateAnalysisApiVersion as ApiVersion, +} from '@kbn/aiops-plugin/common/api/log_rate_analysis/schema'; import type { FtrProviderContext } from '../../ftr_provider_context'; +const API_VERSIONS: ApiVersion[] = ['1', '2']; + export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const config = getService('config'); const kibanaServerUrl = formatUrl(config.get('servers.kibana')); - const requestBody: AiopsApiLogRateAnalysis['body'] = { - baselineMax: 1561719083292, - baselineMin: 1560954147006, - deviationMax: 1562254538692, - deviationMin: 1561986810992, - end: 2147483647000, - index: 'ft_ecommerce', - searchQuery: '{"bool":{"filter":[],"must":[{"match_all":{}}],"must_not":[]}}', - start: 0, - timeFieldName: 'order_date', - }; - describe('POST /internal/aiops/log_rate_analysis', () => { - it('should return permission denied without streaming', async () => { - await supertest - .post(`/internal/aiops/log_rate_analysis`) - .set('kbn-xsrf', 'kibana') - .set(ELASTIC_HTTP_VERSION_HEADER, '1') - .send(requestBody) - .expect(403); - }); + API_VERSIONS.forEach((apiVersion) => { + describe(`with API v${apiVersion}`, () => { + const requestBody: AiopsLogRateAnalysisSchema = { + baselineMax: 1561719083292, + baselineMin: 1560954147006, + deviationMax: 1562254538692, + deviationMin: 1561986810992, + end: 2147483647000, + index: 'ft_ecommerce', + searchQuery: '{"bool":{"filter":[],"must":[{"match_all":{}}],"must_not":[]}}', + start: 0, + timeFieldName: 'order_date', + }; - it('should return permission denied with streaming', async () => { - const response = await fetch(`${kibanaServerUrl}/internal/aiops/log_rate_analysis`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'kbn-xsrf': 'stream', - [ELASTIC_HTTP_VERSION_HEADER]: '1', - }, - body: JSON.stringify(requestBody), - }); + it('should return permission denied without streaming', async () => { + await supertest + .post(`/internal/aiops/log_rate_analysis`) + .set('kbn-xsrf', 'kibana') + .set(ELASTIC_HTTP_VERSION_HEADER, apiVersion) + .send(requestBody) + .expect(403); + }); + + it('should return permission denied with streaming', async () => { + const response = await fetch(`${kibanaServerUrl}/internal/aiops/log_rate_analysis`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'kbn-xsrf': 'stream', + [ELASTIC_HTTP_VERSION_HEADER]: apiVersion, + }, + body: JSON.stringify(requestBody), + }); - expect(response.ok).to.be(false); - expect(response.status).to.be(403); + expect(response.ok).to.be(false); + expect(response.status).to.be(403); + }); + }); }); }); }; diff --git a/x-pack/test/apm_api_integration/tests/mobile/mobile_stats.spec.ts b/x-pack/test/apm_api_integration/tests/mobile/mobile_stats.spec.ts index edc852d97ad2a0..f7bb8f328d220b 100644 --- a/x-pack/test/apm_api_integration/tests/mobile/mobile_stats.spec.ts +++ b/x-pack/test/apm_api_integration/tests/mobile/mobile_stats.spec.ts @@ -10,7 +10,7 @@ import { apm, timerange } from '@kbn/apm-synthtrace-client'; import { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; import { APIReturnType } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; import { ENVIRONMENT_ALL } from '@kbn/apm-plugin/common/environment_filter_values'; -import { sumBy, meanBy } from 'lodash'; +import { meanBy, sumBy } from 'lodash'; import { FtrProviderContext } from '../../common/ftr_provider_context'; type MobileStats = APIReturnType<'GET /internal/apm/mobile-services/{serviceName}/stats'>; @@ -101,6 +101,7 @@ async function generateData({ galaxy10.startNewSession(); huaweiP2.startNewSession(); return [ + galaxy10.appMetrics({ 'application.launch.time': 100 }).timestamp(timestamp), galaxy10 .transaction('Start View - View Appearing', 'Android Activity') .errors(galaxy10.crash({ message: 'error C' }).timestamp(timestamp)) @@ -224,6 +225,14 @@ export default function ApiTest({ getService }: FtrProviderContext) { ); expect(value).to.be(timeseriesMean); }); + it('returns same launch times', () => { + const { value, timeseries } = response.currentPeriod.launchTimes; + const timeseriesMean = meanBy( + timeseries.filter((bucket) => bucket.y !== null), + 'y' + ); + expect(value).to.be(timeseriesMean); + }); }); describe('when filters are applied', () => { @@ -237,6 +246,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.currentPeriod.sessions.value).to.eql(0); expect(response.currentPeriod.requests.value).to.eql(0); expect(response.currentPeriod.crashRate.value).to.eql(0); + expect(response.currentPeriod.launchTimes.value).to.eql(null); expect(response.currentPeriod.sessions.timeseries.every((item) => item.y === 0)).to.eql( true @@ -247,6 +257,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.currentPeriod.crashRate.timeseries.every((item) => item.y === 0)).to.eql( true ); + expect( + response.currentPeriod.launchTimes.timeseries.every((item) => item.y === null) + ).to.eql(true); }); it('returns the correct values when single filter is applied', async () => { @@ -259,6 +272,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.currentPeriod.sessions.value).to.eql(3); expect(response.currentPeriod.requests.value).to.eql(0); expect(response.currentPeriod.crashRate.value).to.eql(3); + expect(response.currentPeriod.launchTimes.value).to.eql(null); }); it('returns the correct values when multiple filters are applied', async () => { @@ -269,6 +283,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.currentPeriod.sessions.value).to.eql(3); expect(response.currentPeriod.requests.value).to.eql(3); expect(response.currentPeriod.crashRate.value).to.eql(1); + expect(response.currentPeriod.launchTimes.value).to.eql(100); }); }); }); diff --git a/x-pack/test/cases_api_integration/common/plugins/cases/server/routes.ts b/x-pack/test/cases_api_integration/common/plugins/cases/server/routes.ts index d68b87a2b132d5..30dc4da6bb3243 100644 --- a/x-pack/test/cases_api_integration/common/plugins/cases/server/routes.ts +++ b/x-pack/test/cases_api_integration/common/plugins/cases/server/routes.ts @@ -5,6 +5,7 @@ * 2.0. */ +import Boom from '@hapi/boom'; import { createHash } from 'crypto'; import { schema } from '@kbn/config-schema'; import type { CoreSetup, Logger } from '@kbn/core/server'; @@ -12,7 +13,7 @@ import type { ExternalReferenceAttachmentType, PersistableStateAttachmentTypeSetup, } from '@kbn/cases-plugin/server/attachment_framework/types'; -import { CasesPatchRequest } from '@kbn/cases-plugin/common/types/api'; +import { BulkCreateCasesRequest, CasesPatchRequest } from '@kbn/cases-plugin/common/types/api'; import type { FixtureStartDeps } from './plugin'; const hashParts = (parts: string[]): string => { @@ -106,4 +107,35 @@ export const registerRoutes = (core: CoreSetup, logger: Logger } } ); + + router.post( + { + path: '/api/cases_fixture/cases:bulkCreate', + validate: { + body: schema.object({}, { unknowns: 'allow' }), + }, + }, + async (context, request, response) => { + try { + const [_, { cases }] = await core.getStartServices(); + const client = await cases.getCasesClientWithRequest(request); + + return response.ok({ + body: await client.cases.bulkCreate(request.body as BulkCreateCasesRequest), + }); + } catch (error) { + logger.error(`Error : ${error}`); + + const boom = new Boom.Boom(error.message, { + statusCode: error.wrappedError.output.statusCode, + }); + + return response.customError({ + body: boom, + headers: boom.output.headers as { [key: string]: string }, + statusCode: boom.output.statusCode, + }); + } + } + ); }; diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/bulk_create_cases.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/bulk_create_cases.ts new file mode 100644 index 00000000000000..8177649f22ab3c --- /dev/null +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/bulk_create_cases.ts @@ -0,0 +1,309 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type SuperTest from 'supertest'; +import expect from '@kbn/expect'; +import { BulkCreateCasesResponse } from '@kbn/cases-plugin/common/types/api'; +import { CaseSeverity } from '@kbn/cases-plugin/common'; +import { CaseStatuses, CustomFieldTypes } from '@kbn/cases-plugin/common/types/domain'; +import { User } from '../../../../common/lib/authentication/types'; +import { defaultUser, getPostCaseRequest, postCaseResp } from '../../../../common/lib/mock'; +import { + deleteAllCaseItems, + getCaseUserActions, + getSpaceUrlPrefix, + removeServerGeneratedPropertiesFromCase, + removeServerGeneratedPropertiesFromUserAction, +} from '../../../../common/lib/api'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; +import { + secOnly, + secOnlyRead, + globalRead, + obsOnlyRead, + obsSecRead, + noKibanaPrivileges, + testDisabled, + superUser, +} from '../../../../common/lib/authentication/users'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext): void => { + const supertestWithoutAuth = getService('supertestWithoutAuth'); + const supertest = getService('supertest'); + const es = getService('es'); + + /** + * There is no official route that supports + * bulk creating cases. The purpose of this test + * is to test the bulkCreate method of the cases client in + * x-pack/plugins/cases/server/client/cases/bulk_create.ts + * + * The test route is configured here x-pack/test/cases_api_integration/common/plugins/cases/server/routes.ts + */ + describe('bulk_create_cases', () => { + const bulkCreateCases = async ({ + superTestService = supertest, + data, + expectedHttpCode = 200, + auth = { user: superUser, space: null }, + }: { + superTestService?: SuperTest.SuperTest; + data: object; + expectedHttpCode?: number; + auth?: { user: User; space: string | null }; + }): Promise => { + return superTestService + .post(`${getSpaceUrlPrefix(auth?.space)}/api/cases_fixture/cases:bulkCreate`) + .set('kbn-xsrf', 'foo') + .set('x-elastic-internal-origin', 'foo') + .auth(auth.user.username, auth.user.password) + .send(data) + .expect(expectedHttpCode) + .then((response) => response.body); + }; + + afterEach(async () => { + await deleteAllCaseItems(es); + }); + + it('should bulk create cases', async () => { + const createdCases = await bulkCreateCases({ + data: { + cases: [getPostCaseRequest(), getPostCaseRequest({ severity: CaseSeverity.MEDIUM })], + }, + }); + + expect(createdCases.cases.length === 2); + + const firstCase = removeServerGeneratedPropertiesFromCase(createdCases.cases[0]); + const secondCase = removeServerGeneratedPropertiesFromCase(createdCases.cases[1]); + + expect(firstCase).to.eql(postCaseResp(null, getPostCaseRequest())); + expect(secondCase).to.eql( + postCaseResp(null, getPostCaseRequest({ severity: CaseSeverity.MEDIUM })) + ); + }); + + it('should bulk create cases with different owners', async () => { + const createdCases = await bulkCreateCases({ + data: { + cases: [getPostCaseRequest(), getPostCaseRequest({ owner: 'observabilityFixture' })], + }, + }); + + expect(createdCases.cases.length === 2); + + const firstCase = removeServerGeneratedPropertiesFromCase(createdCases.cases[0]); + const secondCase = removeServerGeneratedPropertiesFromCase(createdCases.cases[1]); + + expect(firstCase).to.eql(postCaseResp(null, getPostCaseRequest())); + expect(secondCase).to.eql( + postCaseResp(null, getPostCaseRequest({ owner: 'observabilityFixture' })) + ); + }); + + it('should allow creating a case with custom ID', async () => { + const createdCases = await bulkCreateCases({ + data: { + cases: [{ id: 'test-case', ...getPostCaseRequest() }], + }, + }); + + expect(createdCases.cases.length === 1); + + const firstCase = createdCases.cases[0]; + + expect(firstCase.id).to.eql('test-case'); + }); + + it('should validate custom fields correctly', async () => { + await bulkCreateCases({ + data: { + cases: [ + getPostCaseRequest({ + 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', + }, + ], + }), + ], + }, + expectedHttpCode: 400, + }); + }); + + it('should throw an error correctly', async () => { + await bulkCreateCases({ + data: { + cases: [ + // two cases with the same ID will result to a conflict error + { id: 'test-case', ...getPostCaseRequest() }, + { id: 'test-case', ...getPostCaseRequest() }, + ], + }, + expectedHttpCode: 409, + }); + }); + + it('should create user actions correctly', async () => { + const createdCases = await bulkCreateCases({ + data: { + cases: [getPostCaseRequest(), getPostCaseRequest({ severity: CaseSeverity.MEDIUM })], + }, + }); + + const firstCase = createdCases.cases[0]; + const secondCase = createdCases.cases[1]; + + const firstCaseUserActions = await getCaseUserActions({ + supertest, + caseID: firstCase.id, + }); + + const secondCaseUserActions = await getCaseUserActions({ + supertest, + caseID: secondCase.id, + }); + + expect(firstCaseUserActions.length).to.eql(1); + expect(secondCaseUserActions.length).to.eql(1); + + const firstCaseCreationUserAction = removeServerGeneratedPropertiesFromUserAction( + firstCaseUserActions[0] + ); + + const secondCaseCreationUserAction = removeServerGeneratedPropertiesFromUserAction( + secondCaseUserActions[0] + ); + + expect(firstCaseCreationUserAction).to.eql({ + action: 'create', + type: 'create_case', + created_by: defaultUser, + case_id: firstCase.id, + comment_id: null, + owner: 'securitySolutionFixture', + payload: { + description: firstCase.description, + title: firstCase.title, + tags: firstCase.tags, + connector: firstCase.connector, + settings: firstCase.settings, + owner: firstCase.owner, + status: CaseStatuses.open, + severity: CaseSeverity.LOW, + assignees: [], + category: null, + customFields: [], + }, + }); + + expect(secondCaseCreationUserAction).to.eql({ + action: 'create', + type: 'create_case', + created_by: defaultUser, + case_id: secondCase.id, + comment_id: null, + owner: 'securitySolutionFixture', + payload: { + description: secondCase.description, + title: secondCase.title, + tags: secondCase.tags, + connector: secondCase.connector, + settings: secondCase.settings, + owner: secondCase.owner, + status: CaseStatuses.open, + severity: CaseSeverity.MEDIUM, + assignees: [], + category: null, + customFields: [], + }, + }); + }); + + describe('rbac', () => { + it('returns a 403 when attempting to create a case with an owner that was from a disabled feature in the space', async () => { + const theCase = (await bulkCreateCases({ + superTestService: supertestWithoutAuth, + data: { cases: [getPostCaseRequest({ owner: 'testDisabledFixture' })] }, + expectedHttpCode: 403, + auth: { + user: testDisabled, + space: 'space1', + }, + })) as unknown as { message: string }; + + expect(theCase.message).to.eql( + 'Failed to bulk create cases: Error: Unauthorized to create case with owners: "testDisabledFixture"' + ); + }); + + it('User: security solution only - should create a case', async () => { + const cases = await bulkCreateCases({ + superTestService: supertestWithoutAuth, + data: { cases: [getPostCaseRequest({ owner: 'securitySolutionFixture' })] }, + expectedHttpCode: 200, + auth: { + user: secOnly, + space: 'space1', + }, + }); + + expect(cases.cases[0].owner).to.eql('securitySolutionFixture'); + }); + + it('User: security solution only - should NOT create a case of different owner', async () => { + await bulkCreateCases({ + superTestService: supertestWithoutAuth, + data: { cases: [getPostCaseRequest({ owner: 'observabilityFixture' })] }, + expectedHttpCode: 403, + auth: { + user: secOnly, + space: 'space1', + }, + }); + }); + + for (const user of [globalRead, secOnlyRead, obsOnlyRead, obsSecRead, noKibanaPrivileges]) { + it(`User ${ + user.username + } with role(s) ${user.roles.join()} - should NOT create a case`, async () => { + await bulkCreateCases({ + superTestService: supertestWithoutAuth, + data: { cases: [getPostCaseRequest({ owner: 'securitySolutionFixture' })] }, + expectedHttpCode: 403, + auth: { + user, + space: 'space1', + }, + }); + }); + } + + it('should NOT create a case in a space with no permissions', async () => { + await bulkCreateCases({ + superTestService: supertestWithoutAuth, + data: { cases: [getPostCaseRequest({ owner: 'securitySolutionFixture' })] }, + expectedHttpCode: 403, + auth: { + user: secOnly, + space: 'space2', + }, + }); + }); + }); + }); +}; diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/index.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/index.ts index 72d6c093f4bfb4..a0aee09f47ed94 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/index.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/index.ts @@ -10,6 +10,9 @@ import { FtrProviderContext } from '../../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default ({ loadTestFile }: FtrProviderContext): void => { describe('Common', function () { + /** + * Public routes + */ loadTestFile(require.resolve('./client/update_alert_status')); loadTestFile(require.resolve('./comments/delete_comment')); loadTestFile(require.resolve('./comments/delete_comments')); @@ -46,7 +49,6 @@ export default ({ loadTestFile }: FtrProviderContext): void => { /** * Internal routes */ - loadTestFile(require.resolve('./internal/bulk_create_attachments')); loadTestFile(require.resolve('./internal/bulk_get_cases')); loadTestFile(require.resolve('./internal/bulk_get_attachments')); @@ -61,6 +63,11 @@ export default ({ loadTestFile }: FtrProviderContext): void => { loadTestFile(require.resolve('./attachments_framework/external_references.ts')); loadTestFile(require.resolve('./attachments_framework/persistable_state.ts')); + /** + * Cases client + */ + loadTestFile(require.resolve('./cases/bulk_create_cases')); + // NOTE: Migrations are not included because they can inadvertently remove the .kibana indices which removes the users and spaces // which causes errors in any tests after them that relies on those }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/create_rules_bulk.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/create_rules_bulk.ts index 382e902424b5fd..6404da38cdde76 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/create_rules_bulk.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/create_rules_bulk.ts @@ -446,7 +446,9 @@ export default ({ getService }: FtrProviderContext): void => { .send([{ ...getSimpleRule(), investigation_fields: ['foo'] }]) .expect(400); - expect(body.message).to.eql('[request body]: 0: Invalid input'); + expect(body.message).to.eql( + '[request body]: 0.investigation_fields: Expected object, received array, 0.type: Invalid literal value, expected "eql", 0.language: Invalid literal value, expected "eql", 0.investigation_fields: Expected object, received array, 0.investigation_fields: Expected object, received array, and 22 more' + ); }); }); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/index.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/index.ts index 350ac868ac90e5..ae0bfaa8940e8b 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/index.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/index.ts @@ -30,10 +30,5 @@ export default ({ loadTestFile }: FtrProviderContext): void => { loadTestFile(require.resolve('./runtime')); loadTestFile(require.resolve('./throttle')); loadTestFile(require.resolve('./ignore_fields')); - loadTestFile(require.resolve('./risk_engine/init_and_status_apis')); - loadTestFile(require.resolve('./risk_engine/risk_score_preview')); - loadTestFile(require.resolve('./risk_engine/risk_score_calculation')); - loadTestFile(require.resolve('./risk_engine/risk_scoring_task_execution')); - loadTestFile(require.resolve('./risk_engine/telemetry_usage')); }); }; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/patch_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/patch_rules.ts index 1b2fcee9c71433..b2000305a4dbb6 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/patch_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/patch_rules.ts @@ -719,7 +719,9 @@ export default ({ getService }: FtrProviderContext) => { }) .expect(400); - expect(body.message).to.eql('[request body]: Invalid input'); + expect(body.message).to.eql( + '[request body]: investigation_fields: Expected object, received array, investigation_fields: Expected object, received array, investigation_fields: Expected object, received array, investigation_fields: Expected object, received array, investigation_fields: Expected object, received array, and 3 more' + ); }); it('should patch a rule with a legacy investigation field and transform response', async () => { diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/patch_rules_bulk.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/patch_rules_bulk.ts index 4e7010a37dd49b..91c18b2cfa6cca 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/patch_rules_bulk.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/patch_rules_bulk.ts @@ -542,7 +542,9 @@ export default ({ getService }: FtrProviderContext) => { ]) .expect(400); - expect(body.message).to.eql('[request body]: 0: Invalid input'); + expect(body.message).to.eql( + '[request body]: 0.investigation_fields: Expected object, received array, 0.investigation_fields: Expected object, received array, 0.investigation_fields: Expected object, received array, 0.investigation_fields: Expected object, received array, 0.investigation_fields: Expected object, received array, and 3 more' + ); }); it('should patch a rule with a legacy investigation field and transform field in response', async () => { diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action.ts index e9234d47a1816d..2529e794089a9a 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action.ts @@ -16,8 +16,8 @@ import { } from '@kbn/security-solution-plugin/common/constants'; import type { RuleResponse } from '@kbn/security-solution-plugin/common/api/detection_engine'; import { - BulkActionType, - BulkActionEditType, + BulkActionTypeEnum, + BulkActionEditTypeEnum, } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management'; import { getCreateExceptionListDetectionSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; import { EXCEPTION_LIST_ITEM_URL, EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; @@ -106,7 +106,7 @@ export default ({ getService }: FtrProviderContext): void => { await createRule(supertest, log, getSimpleRule()); const { body } = await postBulkAction() - .send({ query: '', action: BulkActionType.export }) + .send({ query: '', action: BulkActionTypeEnum.export }) .expect(200) .expect('Content-Type', 'application/ndjson') .expect('Content-Disposition', 'attachment; filename="rules_export.ndjson"') @@ -177,7 +177,7 @@ export default ({ getService }: FtrProviderContext): void => { }; const { body } = await postBulkAction() - .send({ query: '', action: BulkActionType.export }) + .send({ query: '', action: BulkActionTypeEnum.export }) .expect(200) .expect('Content-Type', 'application/ndjson') .expect('Content-Disposition', 'attachment; filename="rules_export.ndjson"') @@ -234,7 +234,7 @@ export default ({ getService }: FtrProviderContext): void => { await createRule(supertest, log, testRule); const { body } = await postBulkAction() - .send({ query: '', action: BulkActionType.delete }) + .send({ query: '', action: BulkActionTypeEnum.delete }) .expect(200); expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); @@ -269,7 +269,7 @@ export default ({ getService }: FtrProviderContext): void => { expect(sidecarActionsResults.hits.hits[0]?._source?.references[0].id).to.eql(rule1.id); const { body } = await postBulkAction() - .send({ query: '', action: BulkActionType.delete }) + .send({ query: '', action: BulkActionTypeEnum.delete }) .expect(200); expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); @@ -290,7 +290,7 @@ export default ({ getService }: FtrProviderContext): void => { await createRule(supertest, log, getSimpleRule(ruleId)); const { body } = await postBulkAction() - .send({ query: '', action: BulkActionType.enable }) + .send({ query: '', action: BulkActionTypeEnum.enable }) .expect(200); expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); @@ -326,7 +326,7 @@ export default ({ getService }: FtrProviderContext): void => { expect(sidecarActionsResults.hits.hits[0]?._source?.references[0].id).to.eql(rule1.id); const { body } = await postBulkAction() - .send({ query: '', action: BulkActionType.enable }) + .send({ query: '', action: BulkActionTypeEnum.enable }) .expect(200); expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); @@ -363,7 +363,7 @@ export default ({ getService }: FtrProviderContext): void => { await createRule(supertest, log, getSimpleRule(ruleId, true)); const { body } = await postBulkAction() - .send({ query: '', action: BulkActionType.disable }) + .send({ query: '', action: BulkActionTypeEnum.disable }) .expect(200); expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); @@ -399,7 +399,7 @@ export default ({ getService }: FtrProviderContext): void => { expect(sidecarActionsResults.hits.hits[0]?._source?.references[0].id).to.eql(rule1.id); const { body } = await postBulkAction() - .send({ query: '', action: BulkActionType.disable }) + .send({ query: '', action: BulkActionTypeEnum.disable }) .expect(200); expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); @@ -437,7 +437,7 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ query: '', - action: BulkActionType.duplicate, + action: BulkActionTypeEnum.duplicate, duplicate: { include_exceptions: false, include_expired_exceptions: false }, }) .expect(200); @@ -516,7 +516,7 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ query: '', - action: BulkActionType.duplicate, + action: BulkActionTypeEnum.duplicate, duplicate: { include_exceptions: true, include_expired_exceptions: true }, }) .expect(200); @@ -622,7 +622,7 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ query: '', - action: BulkActionType.duplicate, + action: BulkActionTypeEnum.duplicate, duplicate: { include_exceptions: true, include_expired_exceptions: false }, }) .expect(200); @@ -696,7 +696,7 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ query: '', - action: BulkActionType.duplicate, + action: BulkActionTypeEnum.duplicate, duplicate: { include_exceptions: false, include_expired_exceptions: false }, }) .expect(200); @@ -778,10 +778,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body: bulkEditResponse } = await postBulkAction() .send({ query: '', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_tags, + type: BulkActionEditTypeEnum.set_tags, value: tagsToOverwrite, }, ], @@ -835,10 +835,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body: bulkEditResponse } = await postBulkAction() .send({ query: '', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.delete_tags, + type: BulkActionEditTypeEnum.delete_tags, value: tagsToDelete, }, ], @@ -891,10 +891,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body: bulkEditResponse } = await postBulkAction() .send({ query: '', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.add_tags, + type: BulkActionEditTypeEnum.add_tags, value: addedTags, }, ], @@ -925,21 +925,21 @@ export default ({ getService }: FtrProviderContext): void => { existingTags: ['tag1', 'tag2', 'tag3'], tagsToUpdate: [], resultingTags: ['tag1', 'tag2', 'tag3'], - operation: BulkActionEditType.delete_tags, + operation: BulkActionEditTypeEnum.delete_tags, }, { caseName: '0 existing tags - 2 tags = 0 tags', existingTags: [], tagsToUpdate: ['tag4', 'tag5'], resultingTags: [], - operation: BulkActionEditType.delete_tags, + operation: BulkActionEditTypeEnum.delete_tags, }, { caseName: '3 existing tags - 2 other tags (none of them) = 3 tags', existingTags: ['tag1', 'tag2', 'tag3'], tagsToUpdate: ['tag4', 'tag5'], resultingTags: ['tag1', 'tag2', 'tag3'], - operation: BulkActionEditType.delete_tags, + operation: BulkActionEditTypeEnum.delete_tags, }, // Add no-ops { @@ -947,14 +947,14 @@ export default ({ getService }: FtrProviderContext): void => { existingTags: ['tag1', 'tag2', 'tag3'], tagsToUpdate: ['tag1', 'tag2'], resultingTags: ['tag1', 'tag2', 'tag3'], - operation: BulkActionEditType.add_tags, + operation: BulkActionEditTypeEnum.add_tags, }, { caseName: '3 existing tags + 0 tags = 3 tags', existingTags: ['tag1', 'tag2', 'tag3'], tagsToUpdate: [], resultingTags: ['tag1', 'tag2', 'tag3'], - operation: BulkActionEditType.add_tags, + operation: BulkActionEditTypeEnum.add_tags, }, ]; @@ -968,8 +968,8 @@ export default ({ getService }: FtrProviderContext): void => { const { body: bulkEditResponse } = await postBulkAction() .send({ query: '', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { type: operation, value: tagsToUpdate, @@ -1007,10 +1007,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body: bulkEditResponse } = await postBulkAction() .send({ query: '', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_index_patterns, + type: BulkActionEditTypeEnum.set_index_patterns, value: ['initial-index-*'], }, ], @@ -1042,10 +1042,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body: bulkEditResponse } = await postBulkAction() .send({ query: '', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.add_index_patterns, + type: BulkActionEditTypeEnum.add_index_patterns, value: ['index3-*'], }, ], @@ -1079,10 +1079,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body: bulkEditResponse } = await postBulkAction() .send({ query: '', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.delete_index_patterns, + type: BulkActionEditTypeEnum.delete_index_patterns, value: ['index2-*'], }, ], @@ -1113,10 +1113,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [mlRule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.add_index_patterns, + type: BulkActionEditTypeEnum.add_index_patterns, value: ['index-*'], }, ], @@ -1143,10 +1143,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [esqlRule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.add_index_patterns, + type: BulkActionEditTypeEnum.add_index_patterns, value: ['index-*'], }, ], @@ -1176,10 +1176,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [rule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.delete_index_patterns, + type: BulkActionEditTypeEnum.delete_index_patterns, value: ['simple-index-*'], }, ], @@ -1209,10 +1209,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [rule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_index_patterns, + type: BulkActionEditTypeEnum.set_index_patterns, value: [], }, ], @@ -1244,21 +1244,21 @@ export default ({ getService }: FtrProviderContext): void => { existingIndexPatterns: ['index1-*', 'index2-*', 'index3-*'], indexPatternsToUpdate: [], resultingIndexPatterns: ['index1-*', 'index2-*', 'index3-*'], - operation: BulkActionEditType.delete_index_patterns, + operation: BulkActionEditTypeEnum.delete_index_patterns, }, { caseName: '0 existing indeces - 2 indeces = 0 indeces', existingIndexPatterns: [], indexPatternsToUpdate: ['index1-*', 'index2-*'], resultingIndexPatterns: [], - operation: BulkActionEditType.delete_index_patterns, + operation: BulkActionEditTypeEnum.delete_index_patterns, }, { caseName: '3 existing indeces - 2 other indeces (none of them) = 3 indeces', existingIndexPatterns: ['index1-*', 'index2-*', 'index3-*'], indexPatternsToUpdate: ['index8-*', 'index9-*'], resultingIndexPatterns: ['index1-*', 'index2-*', 'index3-*'], - operation: BulkActionEditType.delete_index_patterns, + operation: BulkActionEditTypeEnum.delete_index_patterns, }, // Add no-ops { @@ -1266,14 +1266,14 @@ export default ({ getService }: FtrProviderContext): void => { existingIndexPatterns: ['index1-*', 'index2-*', 'index3-*'], indexPatternsToUpdate: ['index1-*', 'index2-*'], resultingIndexPatterns: ['index1-*', 'index2-*', 'index3-*'], - operation: BulkActionEditType.add_index_patterns, + operation: BulkActionEditTypeEnum.add_index_patterns, }, { caseName: '3 existing indeces + 0 indeces = 3 indeces', existingIndexPatterns: ['index1-*', 'index2-*', 'index3-*'], indexPatternsToUpdate: [], resultingIndexPatterns: ['index1-*', 'index2-*', 'index3-*'], - operation: BulkActionEditType.add_index_patterns, + operation: BulkActionEditTypeEnum.add_index_patterns, }, ]; @@ -1296,8 +1296,8 @@ export default ({ getService }: FtrProviderContext): void => { const { body: bulkEditResponse } = await postBulkAction() .send({ query: '', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { type: operation, value: indexPatternsToUpdate, @@ -1353,10 +1353,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body: setTagsBody } = await postBulkAction().send({ query: '', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_tags, + type: BulkActionEditTypeEnum.set_tags, value: ['reset-tag'], }, ], @@ -1401,10 +1401,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ query: '', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_timeline, + type: BulkActionEditTypeEnum.set_timeline, value: { timeline_id: timelineId, timeline_title: timelineTitle, @@ -1444,10 +1444,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ query: '', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_timeline, + type: BulkActionEditTypeEnum.set_timeline, value: { timeline_id: '', timeline_title: '', @@ -1476,10 +1476,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [mlRule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.add_index_patterns, + type: BulkActionEditTypeEnum.add_index_patterns, value: ['index-*'], }, ], @@ -1509,10 +1509,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [rule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.delete_index_patterns, + type: BulkActionEditTypeEnum.delete_index_patterns, value: ['simple-index-*'], }, ], @@ -1538,10 +1538,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [rule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.add_tags, + type: BulkActionEditTypeEnum.add_tags, value: ['test'], }, ], @@ -1559,35 +1559,35 @@ export default ({ getService }: FtrProviderContext): void => { describe('prebuilt rules', () => { const cases = [ { - type: BulkActionEditType.add_tags, + type: BulkActionEditTypeEnum.add_tags, value: ['new-tag'], }, { - type: BulkActionEditType.set_tags, + type: BulkActionEditTypeEnum.set_tags, value: ['new-tag'], }, { - type: BulkActionEditType.delete_tags, + type: BulkActionEditTypeEnum.delete_tags, value: ['new-tag'], }, { - type: BulkActionEditType.add_index_patterns, + type: BulkActionEditTypeEnum.add_index_patterns, value: ['test-*'], }, { - type: BulkActionEditType.set_index_patterns, + type: BulkActionEditTypeEnum.set_index_patterns, value: ['test-*'], }, { - type: BulkActionEditType.delete_index_patterns, + type: BulkActionEditTypeEnum.delete_index_patterns, value: ['test-*'], }, { - type: BulkActionEditType.set_timeline, + type: BulkActionEditTypeEnum.set_timeline, value: { timeline_id: 'mock-id', timeline_title: 'mock-title' }, }, { - type: BulkActionEditType.set_schedule, + type: BulkActionEditTypeEnum.set_schedule, value: { interval: '1m', lookback: '1m' }, }, ]; @@ -1599,8 +1599,8 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [prebuiltRule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { type, value, @@ -1648,10 +1648,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [createdRule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_rule_actions, + type: BulkActionEditTypeEnum.set_rule_actions, value: { throttle: '1h', actions: [ @@ -1706,10 +1706,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [createdRule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_rule_actions, + type: BulkActionEditTypeEnum.set_rule_actions, value: { throttle: '1h', actions: [ @@ -1766,10 +1766,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [createdRule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_rule_actions, + type: BulkActionEditTypeEnum.set_rule_actions, value: { throttle: '1h', actions: [], @@ -1818,10 +1818,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [createdRule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_rule_actions, + type: BulkActionEditTypeEnum.set_rule_actions, value: { throttle: '1h', actions: [ @@ -1871,10 +1871,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [createdRule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.add_rule_actions, + type: BulkActionEditTypeEnum.add_rule_actions, value: { throttle: '1h', actions: [ @@ -1931,10 +1931,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [createdRule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.add_rule_actions, + type: BulkActionEditTypeEnum.add_rule_actions, value: { throttle: '1h', actions: [ @@ -2004,10 +2004,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [createdRule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.add_rule_actions, + type: BulkActionEditTypeEnum.add_rule_actions, value: { throttle: '1h', actions: [ @@ -2069,10 +2069,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [createdRule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.add_rule_actions, + type: BulkActionEditTypeEnum.add_rule_actions, value: { throttle: '1h', actions: [], @@ -2120,10 +2120,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [createdRule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.add_rule_actions, + type: BulkActionEditTypeEnum.add_rule_actions, value: { throttle: '1h', actions: [], @@ -2147,10 +2147,10 @@ export default ({ getService }: FtrProviderContext): void => { describe('prebuilt rules', () => { const cases = [ { - type: BulkActionEditType.set_rule_actions, + type: BulkActionEditTypeEnum.set_rule_actions, }, { - type: BulkActionEditType.add_rule_actions, + type: BulkActionEditTypeEnum.add_rule_actions, }, ]; cases.forEach(({ type }) => { @@ -2162,8 +2162,8 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [prebuiltRule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { type, value: { @@ -2220,10 +2220,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [prebuiltRule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_rule_actions, + type: BulkActionEditTypeEnum.set_rule_actions, value: { throttle: '1h', actions: [ @@ -2235,7 +2235,7 @@ export default ({ getService }: FtrProviderContext): void => { }, }, { - type: BulkActionEditType.set_tags, + type: BulkActionEditTypeEnum.set_tags, value: ['tag-1'], }, ], @@ -2290,10 +2290,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [createdRule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_rule_actions, + type: BulkActionEditTypeEnum.set_rule_actions, value: { throttle: payloadThrottle, actions: [], @@ -2331,62 +2331,63 @@ export default ({ getService }: FtrProviderContext): void => { expectedThrottle: undefined, }, ]; - [BulkActionEditType.set_rule_actions, BulkActionEditType.add_rule_actions].forEach( - (ruleAction) => { - casesForNonEmptyActions.forEach(({ payloadThrottle, expectedThrottle }) => { - it(`throttle is updated correctly for rule action "${ruleAction}", if payload throttle="${payloadThrottle}" and actions non empty`, async () => { - // create a new connector - const webHookConnector = await createWebHookConnector(); - - const ruleId = 'ruleId'; - const createdRule = await createRule(supertest, log, getSimpleRule(ruleId)); - - const { body } = await postBulkAction() - .send({ - ids: [createdRule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ - { - type: BulkActionEditType.set_rule_actions, - value: { - throttle: payloadThrottle, - actions: [ - { - id: webHookConnector.id, - group: 'default', - params: { body: '{}' }, - }, - ], - }, + [ + BulkActionEditTypeEnum.set_rule_actions, + BulkActionEditTypeEnum.add_rule_actions, + ].forEach((ruleAction) => { + casesForNonEmptyActions.forEach(({ payloadThrottle, expectedThrottle }) => { + it(`throttle is updated correctly for rule action "${ruleAction}", if payload throttle="${payloadThrottle}" and actions non empty`, async () => { + // create a new connector + const webHookConnector = await createWebHookConnector(); + + const ruleId = 'ruleId'; + const createdRule = await createRule(supertest, log, getSimpleRule(ruleId)); + + const { body } = await postBulkAction() + .send({ + ids: [createdRule.id], + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ + { + type: BulkActionEditTypeEnum.set_rule_actions, + value: { + throttle: payloadThrottle, + actions: [ + { + id: webHookConnector.id, + group: 'default', + params: { body: '{}' }, + }, + ], }, - ], - }) - .expect(200); - - // Check that the updated rule is returned with the response - expect(body.attributes.results.updated[0].throttle).to.eql(expectedThrottle); - - const expectedActions = body.attributes.results.updated[0].actions.map( - (action: any) => ({ - ...action, - frequency: { - summary: true, - throttle: payloadThrottle !== 'rule' ? payloadThrottle : null, - notifyWhen: - payloadThrottle !== 'rule' ? 'onThrottleInterval' : 'onActiveAlert', }, - }) - ); + ], + }) + .expect(200); + + // Check that the updated rule is returned with the response + expect(body.attributes.results.updated[0].throttle).to.eql(expectedThrottle); + + const expectedActions = body.attributes.results.updated[0].actions.map( + (action: any) => ({ + ...action, + frequency: { + summary: true, + throttle: payloadThrottle !== 'rule' ? payloadThrottle : null, + notifyWhen: + payloadThrottle !== 'rule' ? 'onThrottleInterval' : 'onActiveAlert', + }, + }) + ); - // Check that the updates have been persisted - const { body: rule } = await fetchRule(ruleId).expect(200); + // Check that the updates have been persisted + const { body: rule } = await fetchRule(ruleId).expect(200); - expect(rule.throttle).to.eql(expectedThrottle); - expect(rule.actions).to.eql(expectedActions); - }); + expect(rule.throttle).to.eql(expectedThrottle); + expect(rule.actions).to.eql(expectedActions); }); - } - ); + }); + }); }); describe('notifyWhen', () => { @@ -2409,10 +2410,10 @@ export default ({ getService }: FtrProviderContext): void => { await postBulkAction() .send({ ids: [createdRule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_rule_actions, + type: BulkActionEditTypeEnum.set_rule_actions, value: { throttle: payload.throttle, actions: [], @@ -2443,10 +2444,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ query: '', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_schedule, + type: BulkActionEditTypeEnum.set_schedule, value: { interval, lookback, @@ -2458,8 +2459,8 @@ export default ({ getService }: FtrProviderContext): void => { expect(body.statusCode).to.eql(400); expect(body.error).to.eql('Bad Request'); - expect(body.message).to.contain('Invalid value "0m" supplied to "edit,value,interval"'); - expect(body.message).to.contain('Invalid value "-1m" supplied to "edit,value,lookback"'); + expect(body.message).to.contain('edit.0.value.interval: Invalid'); + expect(body.message).to.contain('edit.0.value.lookback: Invalid'); }); it('should update schedule values in rules with a valid payload', async () => { @@ -2473,10 +2474,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ query: '', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_schedule, + type: BulkActionEditTypeEnum.set_schedule, value: { interval, lookback, @@ -2511,10 +2512,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body: setIndexBody } = await postBulkAction() .send({ query: '', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.add_index_patterns, + type: BulkActionEditTypeEnum.add_index_patterns, value: ['initial-index-*'], overwrite_data_views: true, }, @@ -2552,10 +2553,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body: setIndexBody } = await postBulkAction() .send({ query: '', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.add_index_patterns, + type: BulkActionEditTypeEnum.add_index_patterns, value: ['initial-index-*'], overwrite_data_views: false, }, @@ -2597,10 +2598,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body: setIndexBody } = await postBulkAction() .send({ query: '', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_index_patterns, + type: BulkActionEditTypeEnum.set_index_patterns, value: ['initial-index-*'], overwrite_data_views: true, }, @@ -2638,10 +2639,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ query: '', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_index_patterns, + type: BulkActionEditTypeEnum.set_index_patterns, value: [], overwrite_data_views: true, }, @@ -2674,10 +2675,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body: setIndexBody } = await postBulkAction() .send({ query: '', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_index_patterns, + type: BulkActionEditTypeEnum.set_index_patterns, value: ['initial-index-*'], overwrite_data_views: false, }, @@ -2720,10 +2721,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [rule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.delete_index_patterns, + type: BulkActionEditTypeEnum.delete_index_patterns, value: ['simple-index-*'], overwrite_data_views: true, }, @@ -2761,10 +2762,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [rule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.delete_index_patterns, + type: BulkActionEditTypeEnum.delete_index_patterns, value: ['simple-index-*'], overwrite_data_views: true, }, @@ -2797,10 +2798,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [rule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.delete_index_patterns, + type: BulkActionEditTypeEnum.delete_index_patterns, value: ['simple-index-*'], overwrite_data_views: false, }, @@ -2830,14 +2831,14 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [rule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.add_index_patterns, + type: BulkActionEditTypeEnum.add_index_patterns, value: ['initial-index-*'], }, { - type: BulkActionEditType.add_tags, + type: BulkActionEditTypeEnum.add_tags, value: ['tag3'], }, ], @@ -2868,16 +2869,16 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [rule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ // Valid operation { - type: BulkActionEditType.add_index_patterns, + type: BulkActionEditTypeEnum.add_index_patterns, value: ['initial-index-*'], }, // Operation to be skipped { - type: BulkActionEditType.add_tags, + type: BulkActionEditTypeEnum.add_tags, value: ['tag1'], }, ], @@ -2908,16 +2909,16 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [rule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ // Operation to be skipped { - type: BulkActionEditType.add_index_patterns, + type: BulkActionEditTypeEnum.add_index_patterns, value: ['index1-*'], }, // Operation to be skipped { - type: BulkActionEditType.add_tags, + type: BulkActionEditTypeEnum.add_tags, value: ['tag1'], }, ], @@ -2949,10 +2950,10 @@ export default ({ getService }: FtrProviderContext): void => { Array.from({ length: 10 }).map(() => postBulkAction().send({ query: '', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_timeline, + type: BulkActionEditTypeEnum.set_timeline, value: { timeline_id: timelineId, timeline_title: timelineTitle, @@ -2978,10 +2979,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ ids: [id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_timeline, + type: BulkActionEditTypeEnum.set_timeline, value: { timeline_id: timelineId, timeline_title: timelineTitle, @@ -3033,7 +3034,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should export rules with legacy investigation_fields and transform legacy field in response', async () => { const { body } = await postBulkAction() - .send({ query: '', action: BulkActionType.export }) + .send({ query: '', action: BulkActionTypeEnum.export }) .expect(200) .expect('Content-Type', 'application/ndjson') .expect('Content-Disposition', 'attachment; filename="rules_export.ndjson"') @@ -3094,7 +3095,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should delete rules with investigation fields and transform legacy field in response', async () => { const { body } = await postBulkAction() - .send({ query: '', action: BulkActionType.delete }) + .send({ query: '', action: BulkActionTypeEnum.delete }) .expect(200); expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 3, total: 3 }); @@ -3124,7 +3125,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should enable rules with legacy investigation fields and transform legacy field in response', async () => { const { body } = await postBulkAction() - .send({ query: '', action: BulkActionType.enable }) + .send({ query: '', action: BulkActionTypeEnum.enable }) .expect(200); expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 3, total: 3 }); @@ -3188,7 +3189,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should disable rules with legacy investigation fields and transform legacy field in response', async () => { const { body } = await postBulkAction() - .send({ query: '', action: BulkActionType.disable }) + .send({ query: '', action: BulkActionTypeEnum.disable }) .expect(200); expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 3, total: 3 }); @@ -3251,7 +3252,7 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postBulkAction() .send({ query: '', - action: BulkActionType.duplicate, + action: BulkActionTypeEnum.duplicate, duplicate: { include_exceptions: false, include_expired_exceptions: false }, }) .expect(200); @@ -3353,10 +3354,10 @@ export default ({ getService }: FtrProviderContext): void => { it('should edit rules with legacy investigation fields', async () => { const { body } = await postBulkAction().send({ query: '', - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_tags, + type: BulkActionEditTypeEnum.set_tags, value: ['reset-tag'], }, ], diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action_dry_run.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action_dry_run.ts index a6df465d09f68b..f1df03187379c6 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action_dry_run.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action_dry_run.ts @@ -12,8 +12,8 @@ import { getCreateEsqlRulesSchemaMock } from '@kbn/security-solution-plugin/comm import expect from 'expect'; import { - BulkActionType, - BulkActionEditType, + BulkActionTypeEnum, + BulkActionEditTypeEnum, } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { @@ -65,7 +65,7 @@ export default ({ getService }: FtrProviderContext): void => { await createRule(supertest, log, getSimpleRule()); const { body } = await postDryRunBulkAction() - .send({ action: BulkActionType.export }) + .send({ action: BulkActionTypeEnum.export }) .expect(400); expect(body).toEqual({ @@ -80,7 +80,7 @@ export default ({ getService }: FtrProviderContext): void => { await createRule(supertest, log, testRule); const { body } = await postDryRunBulkAction() - .send({ action: BulkActionType.delete }) + .send({ action: BulkActionTypeEnum.delete }) .expect(200); expect(body.attributes.summary).toEqual({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); @@ -101,7 +101,7 @@ export default ({ getService }: FtrProviderContext): void => { await createRule(supertest, log, getSimpleRule(ruleId)); const { body } = await postDryRunBulkAction() - .send({ action: BulkActionType.enable }) + .send({ action: BulkActionTypeEnum.enable }) .expect(200); expect(body.attributes.summary).toEqual({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); @@ -123,7 +123,7 @@ export default ({ getService }: FtrProviderContext): void => { await createRule(supertest, log, getSimpleRule(ruleId, true)); const { body } = await postDryRunBulkAction() - .send({ action: BulkActionType.disable }) + .send({ action: BulkActionTypeEnum.disable }) .expect(200); expect(body.attributes.summary).toEqual({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); @@ -146,7 +146,7 @@ export default ({ getService }: FtrProviderContext): void => { await createRule(supertest, log, ruleToDuplicate); const { body } = await postDryRunBulkAction() - .send({ action: BulkActionType.disable }) + .send({ action: BulkActionTypeEnum.disable }) .expect(200); expect(body.attributes.summary).toEqual({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); @@ -172,10 +172,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postDryRunBulkAction() .send({ - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_tags, + type: BulkActionEditTypeEnum.set_tags, value: ['reset-tag'], }, ], @@ -208,10 +208,10 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postDryRunBulkAction() .send({ ids: [immutableRule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { - type: BulkActionEditType.set_tags, + type: BulkActionEditTypeEnum.set_tags, value: ['reset-tag'], }, ], @@ -242,9 +242,9 @@ export default ({ getService }: FtrProviderContext): void => { describe('validate updating index pattern for machine learning rule', () => { const actions = [ - BulkActionEditType.add_index_patterns, - BulkActionEditType.set_index_patterns, - BulkActionEditType.delete_index_patterns, + BulkActionEditTypeEnum.add_index_patterns, + BulkActionEditTypeEnum.set_index_patterns, + BulkActionEditTypeEnum.delete_index_patterns, ]; actions.forEach((editAction) => { @@ -254,8 +254,8 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postDryRunBulkAction() .send({ ids: [mlRule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { type: editAction, value: [], @@ -295,9 +295,9 @@ export default ({ getService }: FtrProviderContext): void => { describe('validate updating index pattern for ES|QL rule', () => { const actions = [ - BulkActionEditType.add_index_patterns, - BulkActionEditType.set_index_patterns, - BulkActionEditType.delete_index_patterns, + BulkActionEditTypeEnum.add_index_patterns, + BulkActionEditTypeEnum.set_index_patterns, + BulkActionEditTypeEnum.delete_index_patterns, ]; actions.forEach((editAction) => { @@ -307,8 +307,8 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await postDryRunBulkAction() .send({ ids: [esqlRule.id], - action: BulkActionType.edit, - [BulkActionType.edit]: [ + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ { type: editAction, value: [], diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/update_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/update_rules.ts index 7c2d88620924f7..d88ed8a898f906 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/update_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/update_rules.ts @@ -566,7 +566,8 @@ export default ({ getService }: FtrProviderContext) => { expect(body).to.eql({ error: 'Bad Request', - message: '[request body]: Invalid input', + message: + '[request body]: type: Invalid literal value, expected "eql", language: Invalid literal value, expected "eql", type: Invalid literal value, expected "query", type: Invalid literal value, expected "saved_query", saved_id: Required, and 14 more', statusCode: 400, }); }); @@ -955,7 +956,9 @@ export default ({ getService }: FtrProviderContext) => { .send(updatedRule) .expect(400); - expect(body.message).to.eql('[request body]: Invalid input'); + expect(body.message).to.eql( + '[request body]: investigation_fields: Expected object, received array, type: Invalid literal value, expected "eql", language: Invalid literal value, expected "eql", investigation_fields: Expected object, received array, investigation_fields: Expected object, received array, and 22 more' + ); }); it('unsets legacy investigation fields when field not specified for update', async () => { diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/update_rules_bulk.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/update_rules_bulk.ts index b5dbf7fca40f26..a3defbb6d1c827 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/update_rules_bulk.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/update_rules_bulk.ts @@ -853,7 +853,9 @@ export default ({ getService }: FtrProviderContext) => { ]) .expect(400); - expect(body.message).to.eql('[request body]: 0: Invalid input'); + expect(body.message).to.eql( + '[request body]: 0.investigation_fields: Expected object, received array, 0.type: Invalid literal value, expected "eql", 0.language: Invalid literal value, expected "eql", 0.investigation_fields: Expected object, received array, 0.investigation_fields: Expected object, received array, and 22 more' + ); }); it('updates a rule with legacy investigation fields and transforms field in response', async () => { diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/new_terms.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/new_terms.ts index 0ac86c991015d8..144d5e9bf51bd2 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/new_terms.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/new_terms.ts @@ -11,10 +11,7 @@ import { v4 as uuidv4 } from 'uuid'; import { NewTermsRuleCreateProps } from '@kbn/security-solution-plugin/common/api/detection_engine'; import { orderBy } from 'lodash'; import { getCreateNewTermsRulesSchemaMock } from '@kbn/security-solution-plugin/common/api/detection_engine/model/rule_schema/mocks'; -import { - getNewTermsRuntimeMappings, - AGG_FIELD_NAME, -} from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_types/new_terms/utils'; + import { getMaxSignalsWarning } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_types/utils/utils'; import { createRule, @@ -23,14 +20,12 @@ import { getOpenSignals, getPreviewAlerts, previewRule, - performSearchQuery, } from '../../utils'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { previewRuleWithExceptionEntries } from '../../utils/preview_rule_with_exception_entries'; import { deleteAllExceptions } from '../../../lists_api_integration/utils'; import { dataGeneratorFactory } from '../../utils/data_generator'; -import { largeArraysBuckets } from './mocks/new_terms'; import { removeRandomValuedProperties } from './utils'; const historicalWindowStart = '2022-10-13T05:00:04.000Z'; @@ -468,7 +463,7 @@ export default ({ getService }: FtrProviderContext) => { const previewAlerts = await getPreviewAlerts({ es, previewId }); expect(previewAlerts.length).eql(1); - expect(previewAlerts[0]._source?.['kibana.alert.new_terms']).eql(['user-0', 'false']); + expect(previewAlerts[0]._source?.['kibana.alert.new_terms']).eql(['user-0', false]); }); it('should generate alerts for every term when history window is small', async () => { @@ -621,6 +616,293 @@ export default ({ getService }: FtrProviderContext) => { expect(previewAlerts.length).eql(100); }); + + it('should not miss alerts if rule execution value combinations number is greater than 100', async () => { + // historical window documents + // 100 combinations for 127.0.0.1 x host-0, host-1, ..., host-100 + const historicalDocuments = [ + { + host: { + name: Array.from(Array(100)).map((_, i) => `host-${100 + i}`), + ip: ['127.0.0.1'], + }, + }, + ]; + + // rule execution documents + // 100 old combinations for 127.0.0.1 x host-0, host-1, ..., host-99 + // 10 new combinations 127.0.0.1 x a-0, a-1, ..., a-9 + const ruleExecutionDocuments = [ + { + host: { + name: [ + ...Array.from(Array(100)).map((_, i) => `host-${100 + i}`), + ...Array.from(Array(10)).map((_, i) => `a-${i}`), + ], + ip: ['127.0.0.1'], + }, + }, + ]; + + const testId = await newTermsTestExecutionSetup({ + historicalDocuments, + ruleExecutionDocuments, + }); + + const rule: NewTermsRuleCreateProps = { + ...getCreateNewTermsRulesSchemaMock('rule-1', true), + index: ['new_terms'], + new_terms_fields: ['host.name', 'host.ip'], + from: ruleExecutionStart, + history_window_start: historicalWindowStart, + query: `id: "${testId}"`, + }; + + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId, size: 200 }); + + // 10 alerts (with host.names a-[0-9]) should be generated + expect(previewAlerts.length).eql(10); + }); + + it('should not miss alerts for high cardinality values in arrays, over 10.000 composite page size', async () => { + // historical window documents + // number of combinations is 50,000 + const historicalDocuments = [ + { + host: { + name: Array.from(Array(100)).map((_, i) => `host-${100 + i}`), + domain: Array.from(Array(100)).map((_, i) => `domain-${100 + i}`), + }, + user: { + name: Array.from(Array(5)).map((_, i) => `user-${100 + i}`), + }, + }, + ]; + + // rule execution documents + // number of combinations is 50,000 + new one + const ruleExecutionDocuments = [ + { + host: { + name: Array.from(Array(100)).map((_, i) => `host-${100 + i}`), + domain: Array.from(Array(100)).map((_, i) => `domain-${100 + i}`), + }, + user: { + name: Array.from(Array(5)).map((_, i) => `user-${100 + i}`), + }, + }, + { + host: { + name: 'host-140', + domain: 'domain-9999', + }, + user: { + name: 'user-9999', + }, + }, + ]; + + const testId = await newTermsTestExecutionSetup({ + historicalDocuments, + ruleExecutionDocuments, + }); + + const rule: NewTermsRuleCreateProps = { + ...getCreateNewTermsRulesSchemaMock('rule-1', true), + index: ['new_terms'], + new_terms_fields: ['host.name', 'host.domain', 'user.name'], + from: ruleExecutionStart, + history_window_start: historicalWindowStart, + query: `id: "${testId}"`, + }; + + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId, size: 200 }); + + // only 1 alert should be generated + expect(previewAlerts.length).eql(1); + }); + + it('should not miss alerts for high cardinality values in arrays, over 10.000 composite page size spread over multiple pages', async () => { + // historical window documents + // number of combinations is 50,000 + const historicalDocuments = [ + { + host: { + name: Array.from(Array(100)).map((_, i) => `host-${100 + i}`), + domain: Array.from(Array(100)).map((_, i) => `domain-${100 + i}`), + }, + user: { + name: Array.from(Array(5)).map((_, i) => `user-${100 + i}`), + }, + }, + ]; + + // rule execution documents + // number of combinations is 50,000 + 4 new ones + const ruleExecutionDocuments = [ + { + host: { + name: Array.from(Array(100)).map((_, i) => `host-${100 + i}`), + domain: Array.from(Array(100)).map((_, i) => `domain-${100 + i}`), + }, + user: { + name: Array.from(Array(5)).map((_, i) => `user-${100 + i}`), + }, + }, + { + host: { + name: 'host-102', + domain: 'domain-9999', + }, + user: { + name: 'user-9999', + }, + }, + { + host: { + name: 'host-140', + domain: 'domain-9999', + }, + user: { + name: 'user-9999', + }, + }, + { + host: { + name: 'host-133', + domain: 'domain-9999', + }, + user: { + name: 'user-9999', + }, + }, + { + host: { + name: 'host-132', + domain: 'domain-9999', + }, + user: { + name: 'user-9999', + }, + }, + ]; + + const testId = await newTermsTestExecutionSetup({ + historicalDocuments, + ruleExecutionDocuments, + }); + + const rule: NewTermsRuleCreateProps = { + ...getCreateNewTermsRulesSchemaMock('rule-1', true), + index: ['new_terms'], + new_terms_fields: ['host.name', 'host.domain', 'user.name'], + from: ruleExecutionStart, + history_window_start: historicalWindowStart, + query: `id: "${testId}"`, + }; + + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId, size: 200 }); + + // only 4 alerts should be generated + expect(previewAlerts.length).eql(4); + }); + + it('should not generate false positive alerts if rule historical window combinations overlap execution ones, which have more than 100', async () => { + // historical window documents + // number of combinations 400: [a, b] x domain-100, domain-101, ..., domain-299 + const historicalDocuments = [ + { + host: { + name: ['a', 'b'], + domain: Array.from(Array(200)).map((_, i) => `domain-${100 + i}`), + }, + }, + ]; + + // rule execution documents + // number of combinations 101: [a] x domain-100, domain-101, ..., domain-199 + b x domain-201 + // no new combination of values emitted + const ruleExecutionDocuments = [ + { + host: { + name: 'a', + domain: Array.from(Array(100)).map((_, i) => `domain-${100 + i}`), + }, + }, + { + host: { + name: 'b', + domain: 'domain-201', + }, + }, + ]; + + const testId = await newTermsTestExecutionSetup({ + historicalDocuments, + ruleExecutionDocuments, + }); + + const rule: NewTermsRuleCreateProps = { + ...getCreateNewTermsRulesSchemaMock('rule-1', true), + index: ['new_terms'], + new_terms_fields: ['host.name', 'host.domain'], + from: ruleExecutionStart, + history_window_start: historicalWindowStart, + query: `id: "${testId}"`, + }; + + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId, size: 200 }); + + expect(previewAlerts.length).eql(0); + }); + + it('should not generate false positive alerts if rule historical window combinations overlap execution ones, which have precisely 100', async () => { + // historical window documents + // number of combinations 400: [a, b] x domain-100, domain-101, ..., domain-299 + const historicalDocuments = [ + { + host: { + name: ['a', 'b'], + domain: Array.from(Array(200)).map((_, i) => `domain-${100 + i}`), + }, + }, + ]; + + // rule execution documents + // number of combinations 100: [a] x domain-100, domain-101, ..., domain-199 + // no new combination of values emitted + const ruleExecutionDocuments = [ + { + host: { + name: 'a', + domain: Array.from(Array(100)).map((_, i) => `domain-${100 + i}`), + }, + }, + ]; + + const testId = await newTermsTestExecutionSetup({ + historicalDocuments, + ruleExecutionDocuments, + }); + + const rule: NewTermsRuleCreateProps = { + ...getCreateNewTermsRulesSchemaMock('rule-1', true), + index: ['new_terms'], + new_terms_fields: ['host.name', 'host.domain'], + from: ruleExecutionStart, + history_window_start: historicalWindowStart, + query: `id: "${testId}"`, + }; + + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId, size: 200 }); + + expect(previewAlerts.length).eql(0); + }); }); describe('timestamp override and fallback', () => { @@ -753,299 +1035,5 @@ export default ({ getService }: FtrProviderContext) => { expect(previewAlerts[0]?._source?.host?.risk?.calculated_score_norm).to.eql(23); }); }); - - describe('runtime field', () => { - it('should return runtime field created from 2 single values', async () => { - // encoded base64 values of "host-0" and "127.0.0.1" joined with underscore - const expectedEncodedValues = ['aG9zdC0w_MTI3LjAuMC4x']; - const { hits } = await performSearchQuery({ - es, - query: { match: { id: 'first_doc' } }, - index: 'new_terms', - fields: [AGG_FIELD_NAME], - runtimeMappings: getNewTermsRuntimeMappings( - ['host.name', 'host.ip'], - [ - { - key: { - 'host.name': 'host-0', - 'host.ip': '127.0.0.1', - }, - doc_count: 1, - }, - ] - ), - }); - - expect(hits.hits[0].fields?.[AGG_FIELD_NAME]).to.eql(expectedEncodedValues); - }); - - it('should not return runtime field created from 2 single values if its value is not in buckets', async () => { - const { hits } = await performSearchQuery({ - es, - query: { match: { id: 'first_doc' } }, - index: 'new_terms', - fields: [AGG_FIELD_NAME], - runtimeMappings: getNewTermsRuntimeMappings( - ['host.name', 'host.ip'], - [ - { - key: { - 'host.name': 'host-0', - }, - doc_count: 1, - }, - ] - ), - }); - - expect(hits.hits[0].fields?.[AGG_FIELD_NAME]).to.be(undefined); - }); - - it('should return runtime field created from 2 single values, including number value', async () => { - // encoded base64 values of "user-0" and 0 joined with underscore - const expectedEncodedValues = ['dXNlci0w_MA==']; - const { hits } = await performSearchQuery({ - es, - query: { match: { id: 'first_doc' } }, - index: 'new_terms', - fields: [AGG_FIELD_NAME], - runtimeMappings: getNewTermsRuntimeMappings( - ['user.name', 'user.id'], - [ - { - key: { - 'user.name': 'user-0', - 'user.id': 0, - }, - doc_count: 1, - }, - ] - ), - }); - - expect(hits.hits[0].fields?.[AGG_FIELD_NAME]).to.eql(expectedEncodedValues); - }); - - it('should return runtime field created from 2 single values, including boolean value', async () => { - // encoded base64 values of "user-0" and true joined with underscore - const expectedEncodedValues = ['dXNlci0w_dHJ1ZQ==']; - const { hits } = await performSearchQuery({ - es, - query: { match: { id: 'first_doc' } }, - index: 'new_terms', - fields: [AGG_FIELD_NAME], - runtimeMappings: getNewTermsRuntimeMappings( - ['user.name', 'user.enabled'], - [ - { - key: { - 'user.name': 'user-0', - 'user.enabled': true, - }, - doc_count: 1, - }, - ] - ), - }); - - expect(hits.hits[0].fields?.[AGG_FIELD_NAME]).to.eql(expectedEncodedValues); - }); - - it('should return runtime field created from 3 single values', async () => { - // encoded base64 values of "host-0" and "127.0.0.1" and "user-0" joined with underscore - const expectedEncodedValues = ['aG9zdC0w_MTI3LjAuMC4x_dXNlci0w']; - const { hits } = await performSearchQuery({ - es, - query: { match: { id: 'first_doc' } }, - index: 'new_terms', - fields: [AGG_FIELD_NAME], - runtimeMappings: getNewTermsRuntimeMappings( - ['host.name', 'host.ip', 'user.name'], - [ - { - key: { - 'host.name': 'host-0', - 'host.ip': '127.0.0.1', - 'user.name': 'user-0', - }, - doc_count: 1, - }, - ] - ), - }); - - expect(hits.hits[0].fields?.[AGG_FIELD_NAME]).to.eql(expectedEncodedValues); - }); - - it('should return runtime field created from fields of arrays', async () => { - // encoded base64 values of all combinations of ["192.168.1.1", "192.168.1.2"] - // and ["tag-new-1", "tag-2", "tag-new-3"] joined with underscore - const expectedEncodedValues = [ - 'MTkyLjE2OC4xLjE=_dGFnLTI=', - 'MTkyLjE2OC4xLjE=_dGFnLW5ldy0x', - 'MTkyLjE2OC4xLjE=_dGFnLW5ldy0z', - 'MTkyLjE2OC4xLjI=_dGFnLTI=', - 'MTkyLjE2OC4xLjI=_dGFnLW5ldy0x', - 'MTkyLjE2OC4xLjI=_dGFnLW5ldy0z', - ]; - const { hits } = await performSearchQuery({ - es, - query: { match: { id: 'doc_with_source_ip_as_array' } }, - index: 'new_terms', - fields: [AGG_FIELD_NAME], - runtimeMappings: getNewTermsRuntimeMappings( - ['source.ip', 'tags'], - [ - { - key: { - tags: 'tag-new-1', - 'source.ip': '192.168.1.1', - }, - doc_count: 1, - }, - { - key: { - tags: 'tag-2', - 'source.ip': '192.168.1.1', - }, - doc_count: 1, - }, - { - key: { - tags: 'tag-new-3', - 'source.ip': '192.168.1.1', - }, - doc_count: 1, - }, - { - key: { - tags: 'tag-new-1', - 'source.ip': '192.168.1.2', - }, - doc_count: 1, - }, - { - key: { - tags: 'tag-2', - 'source.ip': '192.168.1.2', - }, - doc_count: 1, - }, - { - key: { - tags: 'tag-new-3', - 'source.ip': '192.168.1.2', - }, - doc_count: 1, - }, - ] - ), - }); - - expect(hits.hits[0].fields?.[AGG_FIELD_NAME]).to.eql(expectedEncodedValues); - }); - - it('should return runtime field without duplicated values', async () => { - // encoded base64 values of "host-0" and ["tag-1", "tag-2", "tag-2", "tag-1", "tag-1"] - // joined with underscore, without duplicates in tags - const expectedEncodedValues = ['aG9zdC0w_dGFnLTE=', 'aG9zdC0w_dGFnLTI=']; - const { hits } = await performSearchQuery({ - es, - query: { match: { id: 'doc_with_duplicated_tags' } }, - index: 'new_terms', - fields: [AGG_FIELD_NAME], - runtimeMappings: getNewTermsRuntimeMappings( - ['host.name', 'tags'], - [ - { - key: { - tags: 'tag-1', - 'host.name': 'host-0', - }, - doc_count: 1, - }, - { - key: { - tags: 'tag-2', - 'host.name': 'host-0', - }, - doc_count: 1, - }, - ] - ), - }); - - expect(hits.hits[0].fields?.[AGG_FIELD_NAME]).to.eql(expectedEncodedValues); - }); - - it('should not return runtime field if one of fields is null', async () => { - const { hits } = await performSearchQuery({ - es, - query: { match: { id: 'doc_with_null_field' } }, - index: 'new_terms', - fields: [AGG_FIELD_NAME, 'possibly_null_field', 'host.name'], - runtimeMappings: getNewTermsRuntimeMappings( - ['host.name', 'possibly_null_field'], - [ - { - key: { - 'host.name': 'host-0', - possibly_null_field: null, - }, - doc_count: 1, - }, - ] - ), - }); - - expect(hits.hits.length).to.be(1); - expect(hits.hits[0].fields?.[AGG_FIELD_NAME]).to.be(undefined); - expect(hits.hits[0].fields?.possibly_null_field).to.be(undefined); - expect(hits.hits[0].fields?.['host.name']).to.eql(['host-0']); - }); - - it('should not return runtime field if one of fields is not defined in a document', async () => { - const { hits } = await performSearchQuery({ - es, - query: { match: { id: 'doc_without_large_arrays' } }, - index: 'new_terms', - fields: [AGG_FIELD_NAME], - runtimeMappings: getNewTermsRuntimeMappings( - ['host.name', 'large_array_5'], - [ - { - key: { - 'host.name': 'host-0', - }, - doc_count: 1, - }, - ] - ), - }); - - expect(hits.hits.length).to.be(1); - expect(hits.hits[0].fields).to.be(undefined); - }); - - // There is a limit in ES for a number of emitted values in runtime field (100) - // This test makes sure runtime script doesn't cause query failure and returns first 100 results - it('should return runtime field if number of emitted values greater than 100', async () => { - const { hits } = await performSearchQuery({ - es, - query: { match: { id: 'first_doc' } }, - index: 'new_terms', - fields: [AGG_FIELD_NAME], - runtimeMappings: getNewTermsRuntimeMappings( - ['large_array_20', 'large_array_10'], - largeArraysBuckets - ), - }); - - // runtime field should have 100 values, as large_array_20 and large_array_10 - // give in total 200 combinations - expect(hits.hits[0].fields?.[AGG_FIELD_NAME].length).to.be(100); - }); - }); }); }; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/query.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/query.ts index 69425ac7fa4fc4..4c38edaf0cd281 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/query.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/query.ts @@ -28,7 +28,7 @@ import { v4 as uuidv4 } from 'uuid'; import { QueryRuleCreateProps, - BulkActionType, + BulkActionTypeEnum, AlertSuppressionMissingFieldsStrategyEnum, } from '@kbn/security-solution-plugin/common/api/detection_engine'; import { RuleExecutionStatusEnum } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_monitoring'; @@ -2296,7 +2296,7 @@ export default ({ getService }: FtrProviderContext) => { .post(DETECTION_ENGINE_RULES_BULK_ACTION) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') - .send({ query: '', action: BulkActionType.enable }) + .send({ query: '', action: BulkActionTypeEnum.enable }) .expect(200); // Confirming that enabling did not migrate rule, so rule diff --git a/x-pack/test/detection_engine_api_integration/utils/index.ts b/x-pack/test/detection_engine_api_integration/utils/index.ts index 432923baefb1d2..1c62f3dc2c1230 100644 --- a/x-pack/test/detection_engine_api_integration/utils/index.ts +++ b/x-pack/test/detection_engine_api_integration/utils/index.ts @@ -93,12 +93,6 @@ export * from './wait_for_rule_status'; export * from './wait_for_signals_to_be_present'; export * from './prebuilt_rules/create_prebuilt_rule_saved_objects'; export * from './prebuilt_rules/delete_all_prebuilt_rule_assets'; -export * from './prebuilt_rules/delete_prebuilt_rules_fleet_package'; -export * from './prebuilt_rules/get_prebuilt_rules_status'; -export * from './prebuilt_rules/get_prebuilt_rules_and_timelines_status'; -export * from './prebuilt_rules/install_prebuilt_rules_fleet_package'; -export * from './prebuilt_rules/install_prebuilt_rules'; -export * from './prebuilt_rules/upgrade_prebuilt_rules'; export * from './prebuilt_rules/install_mock_prebuilt_rules'; export * from './prebuilt_rules/install_prebuilt_rules_and_timelines'; export * from './get_legacy_action_so'; diff --git a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/get_prebuilt_rules_and_timelines_status.ts b/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/get_prebuilt_rules_and_timelines_status.ts deleted file mode 100644 index 2d03e597dc5aff..00000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/get_prebuilt_rules_and_timelines_status.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { - GetPrebuiltRulesAndTimelinesStatusResponse, - PREBUILT_RULES_STATUS_URL, -} from '@kbn/security-solution-plugin/common/api/detection_engine/prebuilt_rules'; -import type SuperTest from 'supertest'; - -/** - * (LEGACY) - * Helper to retrieve the prebuilt rules status - * - * @param supertest The supertest deps - */ -export const getPrebuiltRulesAndTimelinesStatus = async ( - supertest: SuperTest.SuperTest -): Promise => { - const response = await supertest - .get(PREBUILT_RULES_STATUS_URL) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .send() - .expect(200); - - return response.body; -}; diff --git a/x-pack/test/fleet_api_integration/apis/agents/list.ts b/x-pack/test/fleet_api_integration/apis/agents/list.ts index 995a0393637c65..4bf7e84d70e7f2 100644 --- a/x-pack/test/fleet_api_integration/apis/agents/list.ts +++ b/x-pack/test/fleet_api_integration/apis/agents/list.ts @@ -19,6 +19,7 @@ export default function ({ getService }: FtrProviderContext) { let elasticAgentpkgVersion: string; // Failing: See https://github.com/elastic/kibana/issues/170690 // Failing: See https://github.com/elastic/kibana/issues/170690 + // Failing: See https://github.com/elastic/kibana/issues/170690 describe.skip('fleet_list_agent', () => { before(async () => { await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/fleet/agents'); diff --git a/x-pack/test/fleet_api_integration/apis/agents/unenroll.ts b/x-pack/test/fleet_api_integration/apis/agents/unenroll.ts index e3153ad422ffca..120bd026ec3f12 100644 --- a/x-pack/test/fleet_api_integration/apis/agents/unenroll.ts +++ b/x-pack/test/fleet_api_integration/apis/agents/unenroll.ts @@ -8,6 +8,7 @@ import expect from '@kbn/expect'; import { v4 as uuidv4 } from 'uuid'; +import { AGENTS_INDEX } from '@kbn/fleet-plugin/common'; import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; import { setupFleetAndAgents } from './services'; import { skipIfNoDockerRegistry } from '../../helpers'; @@ -149,7 +150,7 @@ export default function (providerContext: FtrProviderContext) { expect(actionStatus.nbAgentsFailed).to.eql(2); }); - it('/agents/bulk_unenroll should allow to unenroll multiple agents by id from an regular agent policy', async () => { + it('/agents/bulk_unenroll should allow to unenroll multiple agents by id from a regular agent policy', async () => { // set policy to regular await supertest .put(`/api/fleet/agent_policies/policy1`) @@ -188,6 +189,78 @@ export default function (providerContext: FtrProviderContext) { expect(body.total).to.eql(0); }); + it('/agents/bulk_unenroll should allow to unenroll active and inactive agents by kuery with includeInactive', async () => { + // Agent inactive + await esClient.update({ + id: 'agent4', + refresh: 'wait_for', + index: AGENTS_INDEX, + body: { + doc: { + policy_id: 'policy1', + policy_revision_idx: 1, + last_checkin: new Date(Date.now() - 1000 * 60).toISOString(), // policy timeout 1 min + }, + }, + }); + // unenroll all agents that had last checkin before "now" + await supertest + .post(`/api/fleet/agents/bulk_unenroll`) + .set('kbn-xsrf', 'xxx') + .send({ + agents: `last_checkin<="${new Date(Date.now()).toISOString()}"`, + revoke: true, + includeInactive: true, + }) + .expect(200); + + const { body } = await supertest.get(`/api/fleet/agents`); + expect(body.total).to.eql(0); + }); + it('/agents/bulk_unenroll should allow to unenroll inactive agents that never had last checkin by kuery with includeInactive', async () => { + // Agent inactive + await esClient.update({ + id: 'agent4', + refresh: 'wait_for', + index: AGENTS_INDEX, + body: { + doc: { + policy_id: 'policy1', + policy_revision_idx: 1, + last_checkin: new Date(Date.now() - 1000 * 60).toISOString(), // policy timeout 1 min + }, + }, + }); + // agent inactive through enrolled_at as no last_checkin + await esClient.create({ + id: 'agent5', + refresh: 'wait_for', + index: AGENTS_INDEX, + document: { + active: true, + access_api_key_id: 'api-key-4', + policy_id: 'policy1', + type: 'PERMANENT', + local_metadata: { host: { hostname: 'host6' } }, + user_provided_metadata: {}, + enrolled_at: new Date(Date.now() - 1000 * 60).toISOString(), // policy timeout 1 min + }, + }); + // unenroll all agents + await supertest + .post(`/api/fleet/agents/bulk_unenroll`) + .set('kbn-xsrf', 'xxx') + .send({ + agents: 'active: true', + revoke: true, + includeInactive: true, + }) + .expect(200); + + const { body } = await supertest.get(`/api/fleet/agents`); + expect(body.total).to.eql(0); + }); + it('/agents/bulk_unenroll should allow to unenroll multiple agents by kuery in batches async', async () => { const { body } = await supertest .post(`/api/fleet/agents/bulk_unenroll`) diff --git a/x-pack/test/fleet_api_integration/apis/epm/install_endpoint.ts b/x-pack/test/fleet_api_integration/apis/epm/install_endpoint.ts index 2ba688dfb8bf53..892f89f7c2bb6e 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/install_endpoint.ts +++ b/x-pack/test/fleet_api_integration/apis/epm/install_endpoint.ts @@ -15,7 +15,8 @@ export default function (providerContext: FtrProviderContext) { * There are a few features that are only currently supported for the Endpoint * package due to security concerns. */ - describe('Install endpoint package', () => { + // Failing: See https://github.com/elastic/kibana/issues/156941 + describe.skip('Install endpoint package', () => { const { getService } = providerContext; skipIfNoDockerRegistry(providerContext); setupFleetAndAgents(providerContext); diff --git a/x-pack/test/functional/apps/dashboard/group1/feature_controls/dashboard_security.ts b/x-pack/test/functional/apps/dashboard/group1/feature_controls/dashboard_security.ts index 584822a31161bd..dc1cf432f8ca61 100644 --- a/x-pack/test/functional/apps/dashboard/group1/feature_controls/dashboard_security.ts +++ b/x-pack/test/functional/apps/dashboard/group1/feature_controls/dashboard_security.ts @@ -96,7 +96,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('only shows the dashboard navlink', async () => { const navLinks = await appsMenu.readLinks(); - expect(navLinks.map((link) => link.text)).to.eql(['Dashboard', 'Stack Management']); + expect(navLinks.map((link) => link.text)).to.eql(['Dashboards', 'Stack Management']); }); it(`landing page shows "Create new Dashboard" button`, async () => { @@ -286,7 +286,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows dashboard navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Dashboard']); + expect(navLinks).to.eql(['Dashboards']); }); it(`landing page doesn't show "Create new Dashboard" button`, async () => { @@ -398,7 +398,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('shows dashboard navlink', async () => { const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.eql(['Dashboard']); + expect(navLinks).to.eql(['Dashboards']); }); it(`landing page doesn't show "Create new Dashboard" button`, async () => { diff --git a/x-pack/test/functional/apps/dashboard/group1/feature_controls/dashboard_spaces.ts b/x-pack/test/functional/apps/dashboard/group1/feature_controls/dashboard_spaces.ts index ef8c83ec667ba2..2f04d12bdac6d3 100644 --- a/x-pack/test/functional/apps/dashboard/group1/feature_controls/dashboard_spaces.ts +++ b/x-pack/test/functional/apps/dashboard/group1/feature_controls/dashboard_spaces.ts @@ -45,7 +45,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { basePath: '/s/custom_space', }); const navLinks = (await appsMenu.readLinks()).map((link) => link.text); - expect(navLinks).to.contain('Dashboard'); + expect(navLinks).to.contain('Dashboards'); }); it(`landing page shows "Create new Dashboard" button`, async () => { diff --git a/x-pack/test/functional/apps/discover/error_handling.ts b/x-pack/test/functional/apps/discover/error_handling.ts index 46eff2e7d0c896..cf362e8a124759 100644 --- a/x-pack/test/functional/apps/discover/error_handling.ts +++ b/x-pack/test/functional/apps/discover/error_handling.ts @@ -31,7 +31,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // this is the same test as in OSS but it catches different error message issue in different licences describe('invalid scripted field error', () => { it('is rendered', async () => { - expect(await PageObjects.discover.noResultsErrorVisible()).to.be(true); + await PageObjects.discover.showsErrorCallout(); const painlessStackTrace = await testSubjects.find('painlessStackTrace'); expect(painlessStackTrace).not.to.be(undefined); }); diff --git a/x-pack/test/functional/apps/index_management/index_templates_tab/create_index_template.ts b/x-pack/test/functional/apps/index_management/index_templates_tab/create_index_template.ts new file mode 100644 index 00000000000000..9b9d869fb99803 --- /dev/null +++ b/x-pack/test/functional/apps/index_management/index_templates_tab/create_index_template.ts @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default ({ getPageObjects, getService }: FtrProviderContext) => { + const pageObjects = getPageObjects(['common', 'indexManagement', 'header']); + const log = getService('log'); + const security = getService('security'); + const testSubjects = getService('testSubjects'); + + const INDEX_TEMPLATE_NAME = `test-index-template`; + + describe('Create index template', function () { + before(async () => { + await log.debug('Navigating to the index templates tab'); + await security.testUser.setRoles(['index_management_user']); + await pageObjects.common.navigateToApp('indexManagement'); + // Navigate to the data streams tab + await pageObjects.indexManagement.changeTabs('templatesTab'); + await pageObjects.header.waitUntilLoadingHasFinished(); + // Click create template button + await testSubjects.click('createTemplateButton'); + }); + + it('can create an index template with data retention', async () => { + // Complete required fields from step 1 + await testSubjects.setValue('nameField', INDEX_TEMPLATE_NAME); + await testSubjects.setValue('indexPatternsField', 'test-1'); + // Enable data stream + await testSubjects.click('dataStreamField > input'); + // Enable data retention + await testSubjects.click('dataRetentionToggle > input'); + // Set the retention to 7 hours + await testSubjects.setValue('valueDataRetentionField', '7'); + await testSubjects.click('show-filters-button'); + await testSubjects.click('filter-option-h'); + // Navigate to the last step of the wizard + await testSubjects.click('nextButton'); + await testSubjects.click('nextButton'); + await testSubjects.click('nextButton'); + await testSubjects.click('nextButton'); + await testSubjects.click('nextButton'); + + expect(await testSubjects.getVisibleText('lifecycleValue')).to.be('7 hours'); + }); + }); +}; diff --git a/x-pack/test/functional/apps/index_management/index_templates_tab/index.ts b/x-pack/test/functional/apps/index_management/index_templates_tab/index.ts new file mode 100644 index 00000000000000..992c2094c037ef --- /dev/null +++ b/x-pack/test/functional/apps/index_management/index_templates_tab/index.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default ({ loadTestFile }: FtrProviderContext) => { + describe('Index Management: index templates tab', function () { + loadTestFile(require.resolve('./create_index_template')); + }); +}; diff --git a/x-pack/test/functional/apps/infra/home_page.ts b/x-pack/test/functional/apps/infra/home_page.ts index 8c0cc688cb5826..27cc61ca0c273a 100644 --- a/x-pack/test/functional/apps/infra/home_page.ts +++ b/x-pack/test/functional/apps/infra/home_page.ts @@ -238,12 +238,12 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await retry.try(async () => { const nodesWithValue = await pageObjects.infraHome.getNodesWithValues(); expect(nodesWithValue).to.eql([ - { name: 'demo-stack-apache-01', value: 1.4, color: '#6092c0' }, - { name: 'demo-stack-mysql-01', value: 1.2, color: '#82a7cd' }, - { name: 'demo-stack-nginx-01', value: 1.1, color: '#93b1d3' }, - { name: 'demo-stack-redis-01', value: 1, color: '#a2bcd9' }, + { name: 'demo-stack-apache-01', value: 1.2, color: '#6092c0' }, + { name: 'demo-stack-mysql-01', value: 1, color: '#93b1d3' }, + { name: 'demo-stack-nginx-01', value: 0.9, color: '#b2c7df' }, + { name: 'demo-stack-redis-01', value: 0.8, color: '#b2c7df' }, { name: 'demo-stack-haproxy-01', value: 0.8, color: '#c2d2e6' }, - { name: 'demo-stack-client-01', value: 0.6, color: '#f0f4f9' }, + { name: 'demo-stack-client-01', value: 0.5, color: '#f0f4f9' }, ]); }); }); @@ -256,12 +256,12 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await retry.try(async () => { const nodesWithValue = await pageObjects.infraHome.getNodesWithValues(); expect(nodesWithValue).to.eql([ - { name: 'demo-stack-client-01', value: 0.6, color: '#f0f4f9' }, + { name: 'demo-stack-client-01', value: 0.5, color: '#f0f4f9' }, { name: 'demo-stack-haproxy-01', value: 0.8, color: '#c2d2e6' }, - { name: 'demo-stack-redis-01', value: 1, color: '#a2bcd9' }, - { name: 'demo-stack-nginx-01', value: 1.1, color: '#93b1d3' }, - { name: 'demo-stack-mysql-01', value: 1.2, color: '#82a7cd' }, - { name: 'demo-stack-apache-01', value: 1.4, color: '#6092c0' }, + { name: 'demo-stack-redis-01', value: 0.8, color: '#b2c7df' }, + { name: 'demo-stack-nginx-01', value: 0.9, color: '#b2c7df' }, + { name: 'demo-stack-mysql-01', value: 1, color: '#93b1d3' }, + { name: 'demo-stack-apache-01', value: 1.2, color: '#6092c0' }, ]); }); }); @@ -282,7 +282,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await retry.try(async () => { const nodesWithValue = await pageObjects.infraHome.getNodesWithValues(); expect(nodesWithValue).to.eql([ - { name: 'demo-stack-apache-01', value: 1.4, color: '#6092c0' }, + { name: 'demo-stack-apache-01', value: 1.2, color: '#6092c0' }, ]); }); await pageObjects.infraHome.clearSearchTerm(); @@ -295,12 +295,12 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await retry.try(async () => { const nodesWithValue = await pageObjects.infraHome.getNodesWithValues(); expect(nodesWithValue).to.eql([ - { name: 'demo-stack-client-01', value: 0.6, color: '#6092c0' }, + { name: 'demo-stack-client-01', value: 0.5, color: '#6092c0' }, { name: 'demo-stack-haproxy-01', value: 0.8, color: '#b5c9df' }, - { name: 'demo-stack-redis-01', value: 1, color: '#f1d9b9' }, - { name: 'demo-stack-nginx-01', value: 1.1, color: '#eec096' }, - { name: 'demo-stack-mysql-01', value: 1.2, color: '#eba47a' }, - { name: 'demo-stack-apache-01', value: 1.4, color: '#e7664c' }, + { name: 'demo-stack-redis-01', value: 0.8, color: '#d0dcea' }, + { name: 'demo-stack-nginx-01', value: 0.9, color: '#d0dcea' }, + { name: 'demo-stack-mysql-01', value: 1, color: '#eec096' }, + { name: 'demo-stack-apache-01', value: 1.2, color: '#e7664c' }, ]); }); }); diff --git a/x-pack/test/functional/apps/management/feature_controls/management_security.ts b/x-pack/test/functional/apps/management/feature_controls/management_security.ts index d88e2fd8ebe7a4..37066494183460 100644 --- a/x-pack/test/functional/apps/management/feature_controls/management_security.ts +++ b/x-pack/test/functional/apps/management/feature_controls/management_security.ts @@ -36,7 +36,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('should not show the Stack Management nav link', async () => { const links = await appsMenu.readLinks(); - expect(links.map((link) => link.text)).to.eql(['Dashboard']); + expect(links.map((link) => link.text)).to.eql(['Dashboards']); }); it('should render the "application not found" view when navigating to management directly', async () => { diff --git a/x-pack/test/functional/apps/observability_log_explorer/flyout.ts b/x-pack/test/functional/apps/observability_log_explorer/flyout.ts index f8277db16e8db8..b2b71c817255a3 100644 --- a/x-pack/test/functional/apps/observability_log_explorer/flyout.ts +++ b/x-pack/test/functional/apps/observability_log_explorer/flyout.ts @@ -54,9 +54,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); }); - after('clean up archives', async () => { + after('clean up DataStream', async () => { if (cleanupDataStreamSetup) { - cleanupDataStreamSetup(); + await cleanupDataStreamSetup(); } }); diff --git a/x-pack/test/functional/apps/observability_log_explorer/flyout_highlights.ts b/x-pack/test/functional/apps/observability_log_explorer/flyout_highlights.ts new file mode 100644 index 00000000000000..3b71935cd6fbc5 --- /dev/null +++ b/x-pack/test/functional/apps/observability_log_explorer/flyout_highlights.ts @@ -0,0 +1,283 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { FtrProviderContext } from '../../ftr_provider_context'; + +const DATASET_NAME = 'flyout'; +const NAMESPACE = 'default'; +const DATA_STREAM_NAME = `logs-${DATASET_NAME}-${NAMESPACE}`; +const NOW = Date.now(); + +const sharedDoc = { + time: NOW + 1000, + logFilepath: '/flyout.log', + serviceName: 'frontend-node', + datasetName: DATASET_NAME, + namespace: NAMESPACE, + message: 'full document', + logLevel: 'info', + traceId: 'abcdef', + hostName: 'gke-edge-oblt-pool', + orchestratorClusterId: 'my-cluster-id', + orchestratorClusterName: 'my-cluster-id', + orchestratorResourceId: 'orchestratorResourceId', + cloudProvider: 'gcp', + cloudRegion: 'us-central-1', + cloudAz: 'us-central-1a', + cloudProjectId: 'elastic-project', + cloudInstanceId: 'BgfderflkjTheUiGuy', + agentName: 'node', +}; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const dataGrid = getService('dataGrid'); + const testSubjects = getService('testSubjects'); + const PageObjects = getPageObjects(['observabilityLogExplorer']); + + describe('Flyout highlight customization', () => { + let cleanupDataStreamSetup: () => Promise; + + describe('Service container', () => { + const { serviceName, traceId, ...rest } = sharedDoc; + const docWithoutServiceName = { ...rest, traceId, time: NOW - 1000 }; + const docWithoutTraceId = { ...rest, serviceName, time: NOW - 2000 }; + const docWithoutServiceContainer = { ...rest, time: NOW - 4000 }; + + const docs = [ + sharedDoc, + docWithoutServiceName, + docWithoutTraceId, + docWithoutServiceContainer, + ]; + before('setup DataStream', async () => { + cleanupDataStreamSetup = await PageObjects.observabilityLogExplorer.setupDataStream( + DATASET_NAME, + NAMESPACE + ); + await PageObjects.observabilityLogExplorer.ingestLogEntries(DATA_STREAM_NAME, docs); + }); + + after('clean up DataStream', async () => { + if (cleanupDataStreamSetup) { + await cleanupDataStreamSetup(); + } + }); + + beforeEach(async () => { + await PageObjects.observabilityLogExplorer.navigateTo({ + from: new Date(NOW - 60_000).toISOString(), + to: new Date(NOW + 60_000).toISOString(), + }); + }); + + it('should load the service container with all fields', async () => { + await dataGrid.clickRowToggle(); + await testSubjects.existOrFail('logExplorerFlyoutHighlightSectionService'); + await testSubjects.existOrFail('logExplorerFlyoutService'); + await testSubjects.existOrFail('logExplorerFlyoutTrace'); + await dataGrid.closeFlyout(); + }); + + it('should load the service container even when 1 field is missing', async () => { + await dataGrid.clickRowToggle({ rowIndex: 1 }); + await testSubjects.existOrFail('logExplorerFlyoutHighlightSectionService'); + await testSubjects.missingOrFail('logExplorerFlyoutService'); + await testSubjects.existOrFail('logExplorerFlyoutTrace'); + await dataGrid.closeFlyout(); + }); + + it('should not load the service container if all fields are missing', async () => { + await dataGrid.clickRowToggle({ rowIndex: 3 }); + await testSubjects.missingOrFail('logExplorerFlyoutHighlightSectionService'); + await testSubjects.missingOrFail('logExplorerFlyoutService'); + await testSubjects.missingOrFail('logExplorerFlyoutTrace'); + await dataGrid.closeFlyout(); + }); + }); + + describe('Infrastructure container', () => { + const { hostName, orchestratorClusterName, orchestratorResourceId, ...rest } = sharedDoc; + const docWithoutHostName = { + ...rest, + orchestratorClusterName, + orchestratorResourceId, + time: NOW - 1000, + }; + const docWithoutInfrastructureContainer = { ...rest, time: NOW - 2000 }; + + const docs = [sharedDoc, docWithoutHostName, docWithoutInfrastructureContainer]; + before('setup DataStream', async () => { + cleanupDataStreamSetup = await PageObjects.observabilityLogExplorer.setupDataStream( + DATASET_NAME, + NAMESPACE + ); + await PageObjects.observabilityLogExplorer.ingestLogEntries(DATA_STREAM_NAME, docs); + }); + + after('clean up DataStream', async () => { + if (cleanupDataStreamSetup) { + await cleanupDataStreamSetup(); + } + }); + + beforeEach(async () => { + await PageObjects.observabilityLogExplorer.navigateTo({ + from: new Date(NOW - 60_000).toISOString(), + to: new Date(NOW + 60_000).toISOString(), + }); + }); + + it('should load the infrastructure container with all fields', async () => { + await dataGrid.clickRowToggle(); + await testSubjects.existOrFail('logExplorerFlyoutHighlightSectionInfrastructure'); + await testSubjects.existOrFail('logExplorerFlyoutHostName'); + await testSubjects.existOrFail('logExplorerFlyoutClusterName'); + await testSubjects.existOrFail('logExplorerFlyoutResourceId'); + await dataGrid.closeFlyout(); + }); + + it('should load the infrastructure container even when 1 field is missing', async () => { + await dataGrid.clickRowToggle({ rowIndex: 1 }); + await testSubjects.existOrFail('logExplorerFlyoutHighlightSectionInfrastructure'); + await testSubjects.missingOrFail('logExplorerFlyoutHostName'); + await testSubjects.existOrFail('logExplorerFlyoutClusterName'); + await testSubjects.existOrFail('logExplorerFlyoutResourceId'); + await dataGrid.closeFlyout(); + }); + + it('should not load the infrastructure container if all fields are missing', async () => { + await dataGrid.clickRowToggle({ rowIndex: 2 }); + await testSubjects.missingOrFail('logExplorerFlyoutHighlightSectionInfrastructure'); + await testSubjects.missingOrFail('logExplorerFlyoutHostName'); + await testSubjects.missingOrFail('logExplorerFlyoutClusterName'); + await testSubjects.missingOrFail('logExplorerFlyoutResourceId'); + await dataGrid.closeFlyout(); + }); + }); + + describe('Cloud container', () => { + const { cloudProvider, cloudInstanceId, cloudProjectId, cloudRegion, cloudAz, ...rest } = + sharedDoc; + const docWithoutCloudProviderAndInstanceId = { + ...rest, + cloudProjectId, + cloudRegion, + cloudAz, + time: NOW - 1000, + }; + const docWithoutCloudContainer = { ...rest, time: NOW - 2000 }; + + const docs = [sharedDoc, docWithoutCloudProviderAndInstanceId, docWithoutCloudContainer]; + before('setup DataStream', async () => { + cleanupDataStreamSetup = await PageObjects.observabilityLogExplorer.setupDataStream( + DATASET_NAME, + NAMESPACE + ); + await PageObjects.observabilityLogExplorer.ingestLogEntries(DATA_STREAM_NAME, docs); + }); + + after('clean up DataStream', async () => { + if (cleanupDataStreamSetup) { + await cleanupDataStreamSetup(); + } + }); + + beforeEach(async () => { + await PageObjects.observabilityLogExplorer.navigateTo({ + from: new Date(NOW - 60_000).toISOString(), + to: new Date(NOW + 60_000).toISOString(), + }); + }); + + it('should load the cloud container with all fields', async () => { + await dataGrid.clickRowToggle(); + await testSubjects.existOrFail('logExplorerFlyoutHighlightSectionCloud'); + await testSubjects.existOrFail('logExplorerFlyoutCloudProvider'); + await testSubjects.existOrFail('logExplorerFlyoutCloudRegion'); + await testSubjects.existOrFail('logExplorerFlyoutCloudAz'); + await testSubjects.existOrFail('logExplorerFlyoutCloudProjectId'); + await testSubjects.existOrFail('logExplorerFlyoutCloudInstanceId'); + await dataGrid.closeFlyout(); + }); + + it('should load the cloud container even when some fields are missing', async () => { + await dataGrid.clickRowToggle({ rowIndex: 1 }); + await testSubjects.existOrFail('logExplorerFlyoutHighlightSectionCloud'); + + await testSubjects.missingOrFail('logExplorerFlyoutCloudProvider'); + await testSubjects.missingOrFail('logExplorerFlyoutCloudInstanceId'); + + await testSubjects.existOrFail('logExplorerFlyoutCloudRegion'); + await testSubjects.existOrFail('logExplorerFlyoutCloudAz'); + await testSubjects.existOrFail('logExplorerFlyoutCloudProjectId'); + await dataGrid.closeFlyout(); + }); + + it('should not load the cloud container if all fields are missing', async () => { + await dataGrid.clickRowToggle({ rowIndex: 2 }); + await testSubjects.missingOrFail('logExplorerFlyoutHighlightSectionCloud'); + await testSubjects.missingOrFail('logExplorerFlyoutCloudProvider'); + await testSubjects.missingOrFail('logExplorerFlyoutCloudRegion'); + await testSubjects.missingOrFail('logExplorerFlyoutCloudAz'); + await testSubjects.missingOrFail('logExplorerFlyoutCloudProjectId'); + await testSubjects.missingOrFail('logExplorerFlyoutCloudInstanceId'); + await dataGrid.closeFlyout(); + }); + }); + + describe('Other container', () => { + const { logFilepath, agentName, ...rest } = sharedDoc; + const docWithoutLogPathAndAgentName = { + ...rest, + time: NOW - 1000, + }; + + const docs = [sharedDoc, docWithoutLogPathAndAgentName]; + before('setup DataStream', async () => { + cleanupDataStreamSetup = await PageObjects.observabilityLogExplorer.setupDataStream( + DATASET_NAME, + NAMESPACE + ); + await PageObjects.observabilityLogExplorer.ingestLogEntries(DATA_STREAM_NAME, docs); + }); + + after('clean up DataStream', async () => { + if (cleanupDataStreamSetup) { + await cleanupDataStreamSetup(); + } + }); + + beforeEach(async () => { + await PageObjects.observabilityLogExplorer.navigateTo({ + from: new Date(NOW - 60_000).toISOString(), + to: new Date(NOW + 60_000).toISOString(), + }); + }); + + it('should load the other container with all fields', async () => { + await dataGrid.clickRowToggle(); + await testSubjects.existOrFail('logExplorerFlyoutHighlightSectionOther'); + await testSubjects.existOrFail('logExplorerFlyoutLogPathFile'); + await testSubjects.existOrFail('logExplorerFlyoutNamespace'); + await testSubjects.existOrFail('logExplorerFlyoutDataset'); + await testSubjects.existOrFail('logExplorerFlyoutLogShipper'); + await dataGrid.closeFlyout(); + }); + + it('should load the other container even when some fields are missing', async () => { + await dataGrid.clickRowToggle({ rowIndex: 1 }); + await testSubjects.existOrFail('logExplorerFlyoutHighlightSectionOther'); + + await testSubjects.missingOrFail('logExplorerFlyoutLogPathFile'); + await testSubjects.missingOrFail('logExplorerFlyoutLogShipper'); + + await testSubjects.existOrFail('logExplorerFlyoutNamespace'); + await testSubjects.existOrFail('logExplorerFlyoutDataset'); + await dataGrid.closeFlyout(); + }); + }); + }); +} diff --git a/x-pack/test/functional/apps/observability_log_explorer/index.ts b/x-pack/test/functional/apps/observability_log_explorer/index.ts index 948910dab6ab4e..7a767baa887dea 100644 --- a/x-pack/test/functional/apps/observability_log_explorer/index.ts +++ b/x-pack/test/functional/apps/observability_log_explorer/index.ts @@ -16,5 +16,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./filter_controls')); loadTestFile(require.resolve('./flyout')); loadTestFile(require.resolve('./header_menu')); + loadTestFile(require.resolve('./flyout_highlights.ts')); }); } diff --git a/x-pack/test/functional/es_archives/security_solution/new_terms/mappings.json b/x-pack/test/functional/es_archives/security_solution/new_terms/mappings.json index 2f156ddedf5802..ef1868f329e4c4 100644 --- a/x-pack/test/functional/es_archives/security_solution/new_terms/mappings.json +++ b/x-pack/test/functional/es_archives/security_solution/new_terms/mappings.json @@ -12,6 +12,9 @@ }, "host": { "properties": { + "domain": { + "type": "keyword" + }, "name": { "type": "keyword" }, diff --git a/x-pack/test/functional/page_objects/observability_log_explorer.ts b/x-pack/test/functional/page_objects/observability_log_explorer.ts index 2978e3205f4640..0b3266465c32e0 100644 --- a/x-pack/test/functional/page_objects/observability_log_explorer.ts +++ b/x-pack/test/functional/page_objects/observability_log_explorer.ts @@ -406,12 +406,24 @@ export function ObservabilityLogExplorerPageObject({ interface MockLogDoc { time: number; - logFilepath: string; + logFilepath?: string; serviceName?: string; namespace: string; datasetName: string; message?: string; logLevel?: string; + traceId?: string; + hostName?: string; + orchestratorClusterId?: string; + orchestratorClusterName?: string; + orchestratorResourceId?: string; + cloudProvider?: string; + cloudRegion?: string; + cloudAz?: string; + cloudProjectId?: string; + cloudInstanceId?: string; + agentName?: string; + [key: string]: unknown; } @@ -423,6 +435,17 @@ export function createLogDoc({ datasetName, message, logLevel, + traceId, + hostName, + orchestratorClusterId, + orchestratorClusterName, + orchestratorResourceId, + cloudProvider, + cloudRegion, + cloudAz, + cloudProjectId, + cloudInstanceId, + agentName, ...extraFields }: MockLogDoc) { return { @@ -452,6 +475,17 @@ export function createLogDoc({ dataset: datasetName, }, ...(logLevel && { 'log.level': logLevel }), + ...(traceId && { 'trace.id': traceId }), + ...(hostName && { 'host.name': hostName }), + ...(orchestratorClusterId && { 'orchestrator.cluster.id': orchestratorClusterId }), + ...(orchestratorClusterName && { 'orchestrator.cluster.name': orchestratorClusterName }), + ...(orchestratorResourceId && { 'orchestrator.resource.id': orchestratorResourceId }), + ...(cloudProvider && { 'cloud.provider': cloudProvider }), + ...(cloudRegion && { 'cloud.region': cloudRegion }), + ...(cloudAz && { 'cloud.availability_zone': cloudAz }), + ...(cloudProjectId && { 'cloud.project.id': cloudProjectId }), + ...(cloudInstanceId && { 'cloud.instance.id': cloudInstanceId }), + ...(agentName && { 'agent.name': agentName }), ...extraFields, }; } diff --git a/x-pack/test/search_sessions_integration/tests/apps/discover/async_search.ts b/x-pack/test/search_sessions_integration/tests/apps/discover/async_search.ts index 9dc632931d301e..251290c6f4b5ef 100644 --- a/x-pack/test/search_sessions_integration/tests/apps/discover/async_search.ts +++ b/x-pack/test/search_sessions_integration/tests/apps/discover/async_search.ts @@ -62,7 +62,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await browser.navigateTo(savedSessionURL); await PageObjects.header.waitUntilLoadingHasFinished(); await searchSessions.expectState('restored'); - await testSubjects.existOrFail('discoverNoResultsError'); // expect error because of fake searchSessionId + await testSubjects.existOrFail('discoverErrorCalloutTitle'); // expect error because of fake searchSessionId await PageObjects.common.clearAllToasts(); const searchSessionId1 = await getSearchSessionId(); expect(searchSessionId1).to.be(fakeSearchSessionId); @@ -84,10 +84,15 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { // Note this currently fails, for some reason the fakeSearchSessionId is not restored await searchSessions.expectState('restored'); expect(await getSearchSessionId()).to.be(fakeSearchSessionId); + + // back navigation takes discover to fakeSearchSessionId which is in error state + // clean up page to get out of error state before proceeding to next test + await PageObjects.common.clearAllToasts(); + await queryBar.clickQuerySubmitButton(); + await PageObjects.header.waitUntilLoadingHasFinished(); }); it('navigation to context cleans the session', async () => { - await PageObjects.common.clearAllToasts(); const table = await PageObjects.discover.getDocTable(); const isLegacy = await PageObjects.discover.useLegacyTable(); await table.clickRowToggle({ rowIndex: 0 }); diff --git a/x-pack/test/search_sessions_integration/tests/apps/discover/sessions_in_space.ts b/x-pack/test/search_sessions_integration/tests/apps/discover/sessions_in_space.ts index ad1e2cae048777..1bb691629a63ed 100644 --- a/x-pack/test/search_sessions_integration/tests/apps/discover/sessions_in_space.ts +++ b/x-pack/test/search_sessions_integration/tests/apps/discover/sessions_in_space.ts @@ -71,7 +71,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // Check that session is restored await searchSessions.expectState('restored'); - await testSubjects.missingOrFail('discoverNoResultsError'); expect(await toasts.getToastCount()).to.be(0); // no session restoration related warnings }); }); diff --git a/x-pack/test/security_solution_api_integration/README.md b/x-pack/test/security_solution_api_integration/README.md index ebdf649e1e2bb2..c0162a0abb0414 100644 --- a/x-pack/test/security_solution_api_integration/README.md +++ b/x-pack/test/security_solution_api_integration/README.md @@ -28,7 +28,7 @@ ex: ``` -## Adding new security area's tests +# Adding new security area's tests 1. Within the `test_suites` directory, create a new area folder. 2. Introduce `ess.config` and `serverless.config` files to reference the new test files and incorporate any additional custom properties defined in the `CreateTestConfigOptions` interface. @@ -36,7 +36,7 @@ ex: 4. Append a new entry in the `ftr_configs.yml` file to enable the execution of the newly added tests within the CI pipeline. -## Testing locally +# Testing locally In the `package.json` file, you'll find commands to configure the server for each environment and to run tests against that specific environment. These commands adhere to the Mocha tagging system, allowing for the inclusion and exclusion of tags, mirroring the setup of the CI pipeline. @@ -44,49 +44,55 @@ In the `package.json` file, you'll find commands to configure the server for eac In this project, you can run various commands to execute tests and workflows, each of which can be customized by specifying different parameters. Below, how to define the commands based on the parameters and their order. -### Command Structure - -The command structure follows this pattern: - -- ``: The name of the specific command or test case. -- ``: The test folder or workflow you want to run. -- ``: The type of operation, either "server" or "runner." -- ``: The testing environment, such as "serverlessEnv," "essEnv," or "qaEnv." -- ``: The license folder the test is defined under such as "default_license", by default the value is "default_license" -- ``: The area the test is defined under, such as "detection_engine", by default the value is "detection_engine" - -### Serverless and Ess Configuration - -- When using "serverless" or "ess" in the script, it specifies the correct configuration file for the tests. -- "Serverless" and "ess" help determine the configuration specific to the chosen test. - -### serverlessEnv, essEnv, qaEnv Grep Command - -- When using "serverlessEnv,.." in the script, it appends the correct grep command for filtering tests in the serverless testing environment. -- "serverlessEnv,..." is used to customize the test execution based on the serverless environment. - - -### Command Examples - -Here are some command examples using the provided parameters: - -1. **Run the server for "exception_workflows" in the "serverlessEnv" environment:** - ```shell - npm run initialize-server exceptions/workflows serverless - ``` -2. **To run tests for the "exception_workflows" using the serverless runner in the "serverlessEnv" environment, you can use the following command:** - ```shell - npm run run-tests exceptions/workflows serverless serverlessEnv - ``` -3. **Run tests for "exception_workflows" using the serverless runner in the "qaEnv" environment:** - ```shell - npm run run-tests exceptions/workflows serverless qaEnv - ``` -4. **Run the server for "exception_workflows" in the "essEnv" environment:** - ```shell - npm run initialize-server exceptions/workflows ess - ``` -5. **Run tests for "exception_workflows" using the ess runner in the "essEnv" environment:** - ```shell - npm run run-tests exceptions/workflows ess essEnv - ``` \ No newline at end of file +1. Server Initialization and running tests for ex: (Detections Response - Default License): + + The command structure follows this pattern + - `` can be either "server" or "runner," allowing you to either set up the server or execute the tests against the designated server. + - ``: The area the test is defined under, such as "detection_engine, entity_analytics,.." + - ``: The license folder the test is defined under such as "default_license, basic_license,..." + + #### `initialize-server:dr:default` + + - Command: `node ./scripts/index.js server detections_response default_license` + - Description: Initiates the server for the Detections Response area with the default license. + #### `run-tests:dr:default` + + - Command: `node ./scripts/index.js runner detections_response default_license` + - Description: Runs the tests for the Detections Response area with the default license. + + + + 2. Executes particular sets of test suites linked to the designated environment and license: + + The command structure follows this pattern: + + - ``: The test folder or workflow you want to run. + - ``: The type of project to pick the relevant configurations, either "serverless" or "ess." + - "serverless" and "ess" help determine the configuration specific to the chosen test. + - ``: The testing environment, such as "serverlessEnv," "essEnv," or "qaEnv." + - When using "serverlessEnv,.." in the script, it appends the correct grep command for filtering tests in the serverless testing environment. + - "serverlessEnv,..." is used to customize the test execution based on the serverless environment. + + + Here are some command examples for "exceptions" which defined under the "detection_engine" area using the default license: + + 1. **Run the server for "exception_workflows" in the "serverlessEnv" environment:** + ```shell + npm run initialize-server:dr:default exceptions/workflows serverless + ``` + 2. **To run tests for the "exception_workflows" using the serverless runner in the "serverlessEnv" environment, you can use the following command:** + ```shell + npm run run-tests:dr:default exceptions/workflows serverless serverlessEnv + ``` + 3. **Run tests for "exception_workflows" using the serverless runner in the "qaEnv" environment:** + ```shell + npm run run-tests:dr:default exceptions/workflows serverless qaEnv + ``` + 4. **Run the server for "exception_workflows" in the "essEnv" environment:** + ```shell + npm run initialize-server:dr:default exceptions/workflows ess + ``` + 5. **Run tests for "exception_workflows" using the ess runner in the "essEnv" environment:** + ```shell + npm run run-tests:dr:default exceptions/workflows ess essEnv + ``` \ No newline at end of file diff --git a/x-pack/test/security_solution_api_integration/config/serverless/config.base.ts b/x-pack/test/security_solution_api_integration/config/serverless/config.base.ts index 6790c39c851151..6238282722cfc7 100644 --- a/x-pack/test/security_solution_api_integration/config/serverless/config.base.ts +++ b/x-pack/test/security_solution_api_integration/config/serverless/config.base.ts @@ -8,6 +8,8 @@ import { FtrConfigProviderContext } from '@kbn/test'; export interface CreateTestConfigOptions { testFiles: string[]; junit: { reportName: string }; + kbnTestServerArgs?: string[]; + kbnTestServerEnv?: Record; } export function createTestConfig(options: CreateTestConfigOptions) { @@ -20,7 +22,15 @@ export function createTestConfig(options: CreateTestConfigOptions) { ...svlSharedConfig.getAll(), kbnTestServer: { ...svlSharedConfig.get('kbnTestServer'), - serverArgs: [...svlSharedConfig.get('kbnTestServer.serverArgs'), '--serverless=security'], + serverArgs: [ + ...svlSharedConfig.get('kbnTestServer.serverArgs'), + '--serverless=security', + ...(options.kbnTestServerArgs || []), + ], + env: { + ...svlSharedConfig.get('kbnTestServer.env'), + ...options.kbnTestServerEnv, + }, }, testFiles: options.testFiles, junit: options.junit, diff --git a/x-pack/test/security_solution_api_integration/ftr_provider_context.d.ts b/x-pack/test/security_solution_api_integration/ftr_provider_context.d.ts index ae275fcecf99d0..3a44ea9db01fec 100644 --- a/x-pack/test/security_solution_api_integration/ftr_provider_context.d.ts +++ b/x-pack/test/security_solution_api_integration/ftr_provider_context.d.ts @@ -4,7 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { GenericFtrProviderContext } from '@kbn/test'; -import type { FtrProviderContext } from '../../test_serverless/api_integration/ftr_provider_context'; +import { services } from '../../test_serverless/api_integration/services'; -export type { FtrProviderContext }; +export type FtrProviderContext = GenericFtrProviderContext; diff --git a/x-pack/test/security_solution_api_integration/package.json b/x-pack/test/security_solution_api_integration/package.json index 24696f0c00bf06..89f6e0f164f8ed 100644 --- a/x-pack/test/security_solution_api_integration/package.json +++ b/x-pack/test/security_solution_api_integration/package.json @@ -7,6 +7,8 @@ "scripts": { "initialize-server:dr:default": "node ./scripts/index.js server detections_response default_license", "run-tests:dr:default": "node ./scripts/index.js runner detections_response default_license", + "initialize-server:ea:default": "node ./scripts/index.js server entity_analytics default_license", + "run-tests:ea:default": "node ./scripts/index.js runner entity_analytics default_license", "exception_workflows:server:serverless": "npm run initialize-server:dr:default exceptions/workflows serverless", "exception_workflows:runner:serverless": "npm run run-tests:dr:default exceptions/workflows serverless serverlessEnv", "exception_workflows:qa:serverless": "npm run run-tests:dr:default exceptions/workflows serverless qaEnv", @@ -41,6 +43,31 @@ "alerts:runner:serverless": "npm run run-tests:dr:default alerts serverless serverlessEnv", "alerts:qa:serverless": "npm run run-tests:dr:default alerts serverless qaEnv", "alerts:server:ess": "npm run initialize-server:dr:default alerts ess", - "alerts:runner:ess": "npm run run-tests:dr:default alerts ess essEnv" + "alerts:runner:ess": "npm run run-tests:dr:default alerts ess essEnv", + "entity_analytics:server:serverless": "npm run initialize-server:ea:default risk_engine serverless", + "entity_analytics:runner:serverless": "npm run run-tests:ea:default risk_engine serverless serverlessEnv", + "entity_analytics:qa:serverless": "npm run run-tests:ea:default risk_engine serverless qaEnv", + "entity_analytics:server:ess": "npm run initialize-server:ea:default risk_engine ess", + "entity_analytics:runner:ess": "npm run run-tests:ea:default risk_engine ess essEnv", + "prebuilt_rules_management:server:serverless": "npm run initialize-server:dr:default prebuilt_rules/management serverless", + "prebuilt_rules_management:runner:serverless": "npm run run-tests:dr:default prebuilt_rules/management serverless serverlessEnv", + "prebuilt_rules_management:qa:serverless": "npm run run-tests:dr:default prebuilt_rules/management serverless qaEnv", + "prebuilt_rules_management:server:ess": "npm run initialize-server:dr:default prebuilt_rules/management ess", + "prebuilt_rules_management:runner:ess": "npm run run-tests:dr:default prebuilt_rules/management ess essEnv", + "prebuilt_rules_bundled_prebuilt_rules_package:server:serverless": "npm run initialize-server:dr:default prebuilt_rules/bundled_prebuilt_rules_package serverless", + "prebuilt_rules_bundled_prebuilt_rules_package:runner:serverless": "npm run run-tests:dr:default prebuilt_rules/bundled_prebuilt_rules_package serverless serverlessEnv", + "prebuilt_rules_bundled_prebuilt_rules_package:qa:serverless": "npm run run-tests:dr:default prebuilt_rules/bundled_prebuilt_rules_package serverless qaEnv", + "prebuilt_rules_bundled_prebuilt_rules_package:server:ess": "npm run initialize-server:dr:default prebuilt_rules/bundled_prebuilt_rules_package ess", + "prebuilt_rules_bundled_prebuilt_rules_package:runner:ess": "npm run run-tests:dr:default prebuilt_rules/bundled_prebuilt_rules_package ess essEnv", + "prebuilt_rules_large_prebuilt_rules_package:server:serverless": "npm run initialize-server:dr:default prebuilt_rules/large_prebuilt_rules_package serverless", + "prebuilt_rules_large_prebuilt_rules_package:runner:serverless": "npm run run-tests:dr:default prebuilt_rules/large_prebuilt_rules_package serverless serverlessEnv", + "prebuilt_rules_large_prebuilt_rules_package:qa:serverless": "npm run run-tests:dr:default prebuilt_rules/large_prebuilt_rules_package serverless qaEnv", + "prebuilt_rules_large_prebuilt_rules_package:server:ess": "npm run initialize-server:dr:default prebuilt_rules/large_prebuilt_rules_package ess", + "prebuilt_rules_large_prebuilt_rules_package:runner:ess": "npm run run-tests:dr:default prebuilt_rules/large_prebuilt_rules_package ess essEnv", + "prebuilt_rules_update_prebuilt_rules_package:server:serverless": "npm run initialize-server:dr:default prebuilt_rules/update_prebuilt_rules_package serverless", + "prebuilt_rules_update_prebuilt_rules_package:runner:serverless": "npm run run-tests:dr:default prebuilt_rules/update_prebuilt_rules_package serverless serverlessEnv", + "prebuilt_rules_update_prebuilt_rules_package:qa:serverless": "npm run run-tests:dr:default prebuilt_rules/update_prebuilt_rules_package serverless qaEnv", + "prebuilt_rules_update_prebuilt_rules_package:server:ess": "npm run initialize-server:dr:default prebuilt_rules/update_prebuilt_rules_package ess", + "prebuilt_rules_update_prebuilt_rules_package:runner:ess": "npm run run-tests:dr:default prebuilt_rules/update_prebuilt_rules_package ess essEnvs" } } diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/open_close_alerts.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/open_close_alerts.ts index 801791ccc660d1..87d9293cb1bd02 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/open_close_alerts.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/open_close_alerts.ts @@ -47,7 +47,8 @@ export default ({ getService }: FtrProviderContext) => { const dataPathBuilder = new EsArchivePathBuilder(isServerless); const path = dataPathBuilder.getPath('auditbeat/hosts'); - describe('@ess @serverless open_close_alerts', () => { + // Failing: See https://github.com/elastic/kibana/issues/170753 + describe.skip('@ess @serverless open_close_alerts', () => { describe('validation checks', () => { describe('update by ids', () => { it('should not give errors when querying and the alerts index does not exist yet', async () => { diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/bundled_prebuilt_rules_package/config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/bundled_prebuilt_rules_package/configs/ess.config.ts similarity index 78% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/bundled_prebuilt_rules_package/config.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/bundled_prebuilt_rules_package/configs/ess.config.ts index 28f71daa1f0f4c..87c0b1b3c43d87 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/bundled_prebuilt_rules_package/config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/bundled_prebuilt_rules_package/configs/ess.config.ts @@ -4,24 +4,26 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ + import { FtrConfigProviderContext } from '@kbn/test'; import path from 'path'; export const BUNDLED_PACKAGE_DIR = path.join( path.dirname(__filename), - './fleet_bundled_packages/fixtures' + './../fleet_bundled_packages/fixtures' ); -// eslint-disable-next-line import/no-default-export export default async function ({ readConfigFile }: FtrConfigProviderContext) { - const functionalConfig = await readConfigFile(require.resolve('../config.base.ts')); + const functionalConfig = await readConfigFile( + require.resolve('../../../../../../config/ess/config.base.trial') + ); return { ...functionalConfig.getAll(), - testFiles: [ - require.resolve('./prerelease_packages.ts'), - require.resolve('./install_latest_bundled_prebuilt_rules.ts'), - ], + testFiles: [require.resolve('..')], + junit: { + reportName: 'Detection Engine ESS / Bundled Prebuilt Rules Package API Integration Tests', + }, kbnTestServer: { ...functionalConfig.get('kbnTestServer'), serverArgs: [ diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/bundled_prebuilt_rules_package/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/bundled_prebuilt_rules_package/configs/serverless.config.ts new file mode 100644 index 00000000000000..db6e8e11082e0d --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/bundled_prebuilt_rules_package/configs/serverless.config.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import path from 'path'; +import { createTestConfig } from '../../../../../../config/serverless/config.base'; + +export const BUNDLED_PACKAGE_DIR = path.join( + path.dirname(__filename), + './../fleet_bundled_packages/fixtures' +); +export default createTestConfig({ + testFiles: [require.resolve('..')], + junit: { + reportName: + 'Detection Engine Serverless / Bundled Prebuilte Rules Package API Integration Tests', + }, + kbnTestServerArgs: [ + /* Tests in this directory simulate an air-gapped environment in which the instance doesn't have access to EPR. + * To do that, we point the Fleet url to an invalid URL, and instruct Fleet to fetch bundled packages at the + * location defined in BUNDLED_PACKAGE_DIR. + */ + `--xpack.fleet.registryUrl=http://invalidURL:8080`, + `--xpack.fleet.developer.bundledPackageLocation=${BUNDLED_PACKAGE_DIR}`, + ], +}); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/bundled_prebuilt_rules_package/fleet_bundled_packages/fixtures/security_detection_engine-99.0.0.zip b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/bundled_prebuilt_rules_package/fleet_bundled_packages/fixtures/security_detection_engine-99.0.0.zip similarity index 100% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/bundled_prebuilt_rules_package/fleet_bundled_packages/fixtures/security_detection_engine-99.0.0.zip rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/bundled_prebuilt_rules_package/fleet_bundled_packages/fixtures/security_detection_engine-99.0.0.zip diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/bundled_prebuilt_rules_package/fleet_bundled_packages/fixtures/security_detection_engine-99.0.1-beta.1.zip b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/bundled_prebuilt_rules_package/fleet_bundled_packages/fixtures/security_detection_engine-99.0.1-beta.1.zip similarity index 100% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/bundled_prebuilt_rules_package/fleet_bundled_packages/fixtures/security_detection_engine-99.0.1-beta.1.zip rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/bundled_prebuilt_rules_package/fleet_bundled_packages/fixtures/security_detection_engine-99.0.1-beta.1.zip diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/bundled_prebuilt_rules_package/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/bundled_prebuilt_rules_package/index.ts new file mode 100644 index 00000000000000..6d1e677426b329 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/bundled_prebuilt_rules_package/index.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../../../ftr_provider_context'; + +export default ({ loadTestFile }: FtrProviderContext): void => { + describe('Detection Engine API - Bundled Prebuilt Rules Package', function () { + loadTestFile(require.resolve('./install_latest_bundled_prebuilt_rules')); + loadTestFile(require.resolve('./prerelease_packages')); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/bundled_prebuilt_rules_package/install_latest_bundled_prebuilt_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/bundled_prebuilt_rules_package/install_latest_bundled_prebuilt_rules.ts similarity index 86% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/bundled_prebuilt_rules_package/install_latest_bundled_prebuilt_rules.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/bundled_prebuilt_rules_package/install_latest_bundled_prebuilt_rules.ts index d9f710ba6afcf1..bd306b0d65654f 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/bundled_prebuilt_rules_package/install_latest_bundled_prebuilt_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/bundled_prebuilt_rules_package/install_latest_bundled_prebuilt_rules.ts @@ -6,18 +6,18 @@ */ import fs from 'fs/promises'; import path from 'path'; -// @ts-expect-error we have to check types with "allowJs: false" for now, causing this import to fail import { REPO_ROOT } from '@kbn/repo-info'; import JSON5 from 'json5'; import expect from 'expect'; import { PackageSpecManifest } from '@kbn/fleet-plugin/common'; import { ALL_SAVED_OBJECT_INDICES } from '@kbn/core-saved-objects-server'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; -import { deleteAllPrebuiltRuleAssets, deleteAllRules } from '../../utils'; -import { getPrebuiltRulesStatus } from '../../utils/prebuilt_rules/get_prebuilt_rules_status'; -import { installPrebuiltRulesPackageByVersion } from '../../utils/prebuilt_rules/install_fleet_package_by_url'; - -// eslint-disable-next-line import/no-default-export +import { FtrProviderContext } from '../../../../../ftr_provider_context'; +import { + deleteAllRules, + deleteAllPrebuiltRuleAssets, + getPrebuiltRulesStatus, + installPrebuiltRulesPackageByVersion, +} from '../../../utils'; export default ({ getService }: FtrProviderContext): void => { const es = getService('es'); const supertest = getService('supertest'); @@ -28,7 +28,7 @@ export default ({ getService }: FtrProviderContext): void => { /* attempt to install it from the local file system. The API response from EPM provides /* us with the information of whether the package was installed from the registry or /* from a package that was bundled with Kibana */ - describe('install_bundled_prebuilt_rules', () => { + describe('@ess @serverless @skipInQA install_bundled_prebuilt_rules', () => { beforeEach(async () => { await deleteAllRules(supertest, log); await deleteAllPrebuiltRuleAssets(es); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/bundled_prebuilt_rules_package/prerelease_packages.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/bundled_prebuilt_rules_package/prerelease_packages.ts similarity index 70% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/bundled_prebuilt_rules_package/prerelease_packages.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/bundled_prebuilt_rules_package/prerelease_packages.ts index 7348b596d1404e..448e325892a5fd 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/bundled_prebuilt_rules_package/prerelease_packages.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/bundled_prebuilt_rules_package/prerelease_packages.ts @@ -5,14 +5,19 @@ * 2.0. */ import expect from 'expect'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; -import { deleteAllPrebuiltRuleAssets, deleteAllRules } from '../../utils'; -import { getInstalledRules } from '../../utils/prebuilt_rules/get_installed_rules'; -import { getPrebuiltRulesStatus } from '../../utils/prebuilt_rules/get_prebuilt_rules_status'; -import { installPrebuiltRulesPackageViaFleetAPI } from '../../utils/prebuilt_rules/install_fleet_package_by_url'; -import { installPrebuiltRules } from '../../utils/prebuilt_rules/install_prebuilt_rules'; -// eslint-disable-next-line import/no-default-export +import { FtrProviderContext } from '../../../../../ftr_provider_context'; +import { + deleteAllPrebuiltRuleAssets, + deleteAllRules, + deletePrebuiltRulesFleetPackage, + getInstalledRules, + getPrebuiltRulesFleetPackage, + getPrebuiltRulesStatus, + installPrebuiltRules, + installPrebuiltRulesPackageViaFleetAPI, +} from '../../../utils'; + export default ({ getService }: FtrProviderContext): void => { const es = getService('es'); const supertest = getService('supertest'); @@ -25,10 +30,11 @@ export default ({ getService }: FtrProviderContext): void => { /* (We use high mock version numbers to prevent clashes with real packages downloaded in other tests.) /* To do assertions on which packages have been installed, 99.0.0 has a single rule to install, /* while 99.0.1-beta.1 has 2 rules to install. Also, both packages have the version as part of the rule names. */ - describe('prerelease_packages', () => { + describe('@ess @serverless @skipInQA prerelease_packages', () => { beforeEach(async () => { await deleteAllRules(supertest, log); await deleteAllPrebuiltRuleAssets(es); + await deletePrebuiltRulesFleetPackage(supertest); }); it('should install latest stable version and ignore prerelease packages', async () => { @@ -38,13 +44,27 @@ export default ({ getService }: FtrProviderContext): void => { expect(statusBeforePackageInstallation.stats.num_prebuilt_rules_to_install).toBe(0); expect(statusBeforePackageInstallation.stats.num_prebuilt_rules_to_upgrade).toBe(0); - await installPrebuiltRulesPackageViaFleetAPI(es, supertest); + // Install package without specifying version to check if latest stable version is installed + const fleetPackageInstallationResponse = await installPrebuiltRulesPackageViaFleetAPI( + es, + supertest + ); + + expect(fleetPackageInstallationResponse.items.length).toBe(1); + expect(fleetPackageInstallationResponse.items[0].id).toBe('rule_99.0.0'); // Name of the rule in package 99.0.0 + + // Get the installed package and check if the version is 99.0.0 + const prebuiltRulesFleetPackage = await getPrebuiltRulesFleetPackage(supertest); + expect(prebuiltRulesFleetPackage.body.item.version).toBe('99.0.0'); + expect(prebuiltRulesFleetPackage.status).toBe(200); + // Get status of our prebuilt rules (nothing should be instaled yet) const statusAfterPackageInstallation = await getPrebuiltRulesStatus(supertest); expect(statusAfterPackageInstallation.stats.num_prebuilt_rules_installed).toBe(0); expect(statusAfterPackageInstallation.stats.num_prebuilt_rules_to_install).toBe(1); // 1 rule in package 99.0.0 expect(statusAfterPackageInstallation.stats.num_prebuilt_rules_to_upgrade).toBe(0); + // Install prebuilt rules await installPrebuiltRules(es, supertest); // Verify that status is updated after package installation diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/large_prebuilt_rules_package/config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/large_prebuilt_rules_package/configs/ess.config.ts similarity index 80% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/large_prebuilt_rules_package/config.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/large_prebuilt_rules_package/configs/ess.config.ts index cba74885725939..9b056de5b82524 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/large_prebuilt_rules_package/config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/large_prebuilt_rules_package/configs/ess.config.ts @@ -4,21 +4,27 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ + import { FtrConfigProviderContext } from '@kbn/test'; import path from 'path'; export const BUNDLED_PACKAGE_DIR = path.join( path.dirname(__filename), - './fleet_bundled_packages/fixtures' + './../fleet_bundled_packages/fixtures' ); -// eslint-disable-next-line import/no-default-export export default async function ({ readConfigFile }: FtrConfigProviderContext) { - const functionalConfig = await readConfigFile(require.resolve('../config.base.ts')); + const functionalConfig = await readConfigFile( + require.resolve('../../../../../../config/ess/config.base.trial') + ); return { ...functionalConfig.getAll(), - testFiles: [require.resolve('./install_large_prebuilt_rules_package.ts')], + testFiles: [require.resolve('..')], + junit: { + reportName: + 'Detection Engine ESS / Large Prebuilt Rules Package Installation API Integration Tests', + }, kbnTestServer: { ...functionalConfig.get('kbnTestServer'), serverArgs: [ @@ -36,7 +42,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { /* Limit the heap memory to the lowest amount with which Kibana doesn't crash with an out of memory error * when installing the large package. */ - NODE_OPTIONS: '--max-old-space-size=700', + NODE_OPTIONS: '--max-old-space-size=800', }, }, }; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/large_prebuilt_rules_package/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/large_prebuilt_rules_package/configs/serverless.config.ts new file mode 100644 index 00000000000000..29b6ec1c4cc6cb --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/large_prebuilt_rules_package/configs/serverless.config.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import path from 'path'; +import { createTestConfig } from '../../../../../../config/serverless/config.base'; + +export const BUNDLED_PACKAGE_DIR = path.join( + path.dirname(__filename), + './../fleet_bundled_packages/fixtures' +); +export default createTestConfig({ + testFiles: [require.resolve('..')], + junit: { + reportName: + 'Detection Engine Serverless / Large Prebuilt Rules Package Installation API Integration Tests', + }, + kbnTestServerArgs: [ + /* Tests in this directory simulate an air-gapped environment in which the instance doesn't have access to EPR. + * To do that, we point the Fleet url to an invalid URL, and instruct Fleet to fetch bundled packages at the + * location defined in BUNDLED_PACKAGE_DIR. + * Since we want to test the installation of a large package, we created a specific package `security_detection_engine-100.0.0` + * which contains 15000 rules assets and 750 unique rules, and attempt to install it. + */ + `--xpack.fleet.registryUrl=http://invalidURL:8080`, + `--xpack.fleet.developer.bundledPackageLocation=${BUNDLED_PACKAGE_DIR}`, + ], + kbnTestServerEnv: { + /* Limit the heap memory to the lowest amount with which Kibana doesn't crash with an out of memory error + * when installing the large package. + */ + NODE_OPTIONS: '--max-old-space-size=800', + }, +}); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/large_prebuilt_rules_package/fleet_bundled_packages/fixtures/security_detection_engine-100.0.0.zip b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/large_prebuilt_rules_package/fleet_bundled_packages/fixtures/security_detection_engine-100.0.0.zip similarity index 100% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/large_prebuilt_rules_package/fleet_bundled_packages/fixtures/security_detection_engine-100.0.0.zip rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/large_prebuilt_rules_package/fleet_bundled_packages/fixtures/security_detection_engine-100.0.0.zip diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/large_prebuilt_rules_package/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/large_prebuilt_rules_package/index.ts new file mode 100644 index 00000000000000..74959124978aed --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/large_prebuilt_rules_package/index.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../../../ftr_provider_context'; + +export default ({ loadTestFile }: FtrProviderContext): void => { + describe('Detection Engine API - Large Prebuilt Rules Package', function () { + loadTestFile(require.resolve('./install_large_prebuilt_rules_package')); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/large_prebuilt_rules_package/install_large_prebuilt_rules_package.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/large_prebuilt_rules_package/install_large_prebuilt_rules_package.ts similarity index 78% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/large_prebuilt_rules_package/install_large_prebuilt_rules_package.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/large_prebuilt_rules_package/install_large_prebuilt_rules_package.ts index c047413bdb90ab..e059cab5ae64b2 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/large_prebuilt_rules_package/install_large_prebuilt_rules_package.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/large_prebuilt_rules_package/install_large_prebuilt_rules_package.ts @@ -5,18 +5,20 @@ * 2.0. */ import expect from 'expect'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; -import { deleteAllRules, getPrebuiltRulesAndTimelinesStatus } from '../../utils'; -import { deleteAllPrebuiltRuleAssets } from '../../utils/prebuilt_rules/delete_all_prebuilt_rule_assets'; -import { installPrebuiltRulesAndTimelines } from '../../utils/prebuilt_rules/install_prebuilt_rules_and_timelines'; +import { FtrProviderContext } from '../../../../../ftr_provider_context'; +import { + deleteAllPrebuiltRuleAssets, + deleteAllRules, + getPrebuiltRulesAndTimelinesStatus, + installPrebuiltRulesAndTimelines, +} from '../../../utils'; -// eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const es = getService('es'); const supertest = getService('supertest'); const log = getService('log'); - describe('install_large_prebuilt_rules_package', () => { + describe('@ess @serverless @skipInQA install_large_prebuilt_rules_package', () => { beforeEach(async () => { await deleteAllRules(supertest, log); await deleteAllPrebuiltRuleAssets(es); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/update_prebuilt_rules_package/config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/management/configs/ess.config.ts similarity index 61% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/update_prebuilt_rules_package/config.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/management/configs/ess.config.ts index 63e43d962a52e7..7fec27a5d9276f 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/update_prebuilt_rules_package/config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/management/configs/ess.config.ts @@ -7,12 +7,16 @@ import { FtrConfigProviderContext } from '@kbn/test'; -// eslint-disable-next-line import/no-default-export export default async function ({ readConfigFile }: FtrConfigProviderContext) { - const functionalConfig = await readConfigFile(require.resolve('../config.base.ts')); + const functionalConfig = await readConfigFile( + require.resolve('../../../../../../config/ess/config.base.trial') + ); return { ...functionalConfig.getAll(), - testFiles: [require.resolve('./update_prebuilt_rules_package.ts')], + testFiles: [require.resolve('..')], + junit: { + reportName: 'Detection Engine ESS / Prebuilt Rules Management API Integration Tests', + }, }; } diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/management/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/management/configs/serverless.config.ts new file mode 100644 index 00000000000000..89916d26e7a736 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/management/configs/serverless.config.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 { createTestConfig } from '../../../../../../config/serverless/config.base'; + +export default createTestConfig({ + testFiles: [require.resolve('..')], + junit: { + reportName: 'Detection Engine Serverless / Prebuilt Rules Management API Integration Tests', + }, +}); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/fleet_integration.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/management/fleet_integration.ts similarity index 79% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/fleet_integration.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/management/fleet_integration.ts index 1433cb7cac2fff..0eff0a25c2cb82 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/fleet_integration.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/management/fleet_integration.ts @@ -5,24 +5,23 @@ * 2.0. */ import expect from 'expect'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { FtrProviderContext } from '../../../../../ftr_provider_context'; import { deleteAllRules, - deleteAllTimelines, getPrebuiltRulesAndTimelinesStatus, -} from '../../utils'; -import { deleteAllPrebuiltRuleAssets } from '../../utils/prebuilt_rules/delete_all_prebuilt_rule_assets'; -import { installPrebuiltRulesFleetPackage } from '../../utils/prebuilt_rules/install_prebuilt_rules_fleet_package'; -import { installPrebuiltRulesAndTimelines } from '../../utils/prebuilt_rules/install_prebuilt_rules_and_timelines'; -import { deletePrebuiltRulesFleetPackage } from '../../utils/prebuilt_rules/delete_prebuilt_rules_fleet_package'; + installPrebuiltRulesAndTimelines, +} from '../../../utils'; +import { deleteAllPrebuiltRuleAssets } from '../../../utils/rules/prebuilt_rules/delete_all_prebuilt_rule_assets'; +import { deleteAllTimelines } from '../../../utils/rules/prebuilt_rules/delete_all_timelines'; +import { deletePrebuiltRulesFleetPackage } from '../../../utils/rules/prebuilt_rules/delete_prebuilt_rules_fleet_package'; +import { installPrebuiltRulesFleetPackage } from '../../../utils/rules/prebuilt_rules/install_prebuilt_rules_fleet_package'; -// eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const es = getService('es'); const supertest = getService('supertest'); const log = getService('log'); - describe('install_prebuilt_rules_from_real_package', () => { + describe('@ess @serverless @skipInQA install_prebuilt_rules_from_real_package', () => { beforeEach(async () => { await deletePrebuiltRulesFleetPackage(supertest); await deleteAllRules(supertest, log); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/get_prebuilt_rules_status.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/management/get_prebuilt_rules_status.ts similarity index 96% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/get_prebuilt_rules_status.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/management/get_prebuilt_rules_status.ts index ae43e3bdd50982..16dba276169474 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/get_prebuilt_rules_status.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/management/get_prebuilt_rules_status.ts @@ -6,32 +6,29 @@ */ import expect from 'expect'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { FtrProviderContext } from '../../../../../ftr_provider_context'; import { - createRule, + deleteAllPrebuiltRuleAssets, deleteAllRules, + getPrebuiltRulesStatus, + createRule, + getSimpleRule, + createRuleAssetSavedObject, + createPrebuiltRuleAssetSavedObjects, + installPrebuiltRules, deleteRule, + upgradePrebuiltRules, + createHistoricalPrebuiltRuleAssetSavedObjects, getPrebuiltRulesAndTimelinesStatus, - getSimpleRule, installPrebuiltRulesAndTimelines, -} from '../../utils'; -import { - createHistoricalPrebuiltRuleAssetSavedObjects, - createPrebuiltRuleAssetSavedObjects, - createRuleAssetSavedObject, -} from '../../utils/prebuilt_rules/create_prebuilt_rule_saved_objects'; -import { deleteAllPrebuiltRuleAssets } from '../../utils/prebuilt_rules/delete_all_prebuilt_rule_assets'; -import { getPrebuiltRulesStatus } from '../../utils/prebuilt_rules/get_prebuilt_rules_status'; -import { installPrebuiltRules } from '../../utils/prebuilt_rules/install_prebuilt_rules'; -import { upgradePrebuiltRules } from '../../utils/prebuilt_rules/upgrade_prebuilt_rules'; +} from '../../../utils'; -// eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const es = getService('es'); const log = getService('log'); - describe('Prebuilt Rules status', () => { + describe('@ess @serverless @skipInQA Prebuilt Rules status', () => { describe('get_prebuilt_rules_status', () => { beforeEach(async () => { await deleteAllPrebuiltRuleAssets(es); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/get_prebuilt_timelines_status.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/management/get_prebuilt_timelines_status.ts similarity index 88% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/get_prebuilt_timelines_status.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/management/get_prebuilt_timelines_status.ts index 05b34ffa98ed7e..9acef16bfbeb14 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/get_prebuilt_timelines_status.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/management/get_prebuilt_timelines_status.ts @@ -6,19 +6,18 @@ */ import expect from 'expect'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { FtrProviderContext } from '../../../../../ftr_provider_context'; import { deleteAllTimelines, getPrebuiltRulesAndTimelinesStatus, installPrebuiltRulesAndTimelines, -} from '../../utils'; +} from '../../../utils'; -// eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const es = getService('es'); - describe('get_prebuilt_timelines_status', () => { + describe('@ess @serverless @skipInQA get_prebuilt_timelines_status', () => { beforeEach(async () => { await deleteAllTimelines(es); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/management/index.ts similarity index 73% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/index.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/management/index.ts index 67ae12c3573518..52a745e0e79757 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/index.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/management/index.ts @@ -5,11 +5,10 @@ * 2.0. */ -import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { FtrProviderContext } from '../../../../../ftr_provider_context'; -// eslint-disable-next-line import/no-default-export export default ({ loadTestFile }: FtrProviderContext): void => { - describe('detection engine api security and spaces enabled - Prebuilt Rules', function () { + describe('Detection Engine API - Prebuilt Rules Management', function () { loadTestFile(require.resolve('./get_prebuilt_rules_status')); loadTestFile(require.resolve('./get_prebuilt_timelines_status')); loadTestFile(require.resolve('./install_and_upgrade_prebuilt_rules')); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/install_and_upgrade_prebuilt_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/management/install_and_upgrade_prebuilt_rules.ts similarity index 95% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/install_and_upgrade_prebuilt_rules.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/management/install_and_upgrade_prebuilt_rules.ts index 85af64415c95e2..a75c8f87bd783c 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/install_and_upgrade_prebuilt_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/management/install_and_upgrade_prebuilt_rules.ts @@ -4,34 +4,30 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - import expect from 'expect'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { FtrProviderContext } from '../../../../../ftr_provider_context'; import { deleteAllRules, deleteAllTimelines, + deleteAllPrebuiltRuleAssets, + createRuleAssetSavedObject, + createPrebuiltRuleAssetSavedObjects, + installPrebuiltRulesAndTimelines, deleteRule, getPrebuiltRulesAndTimelinesStatus, -} from '../../utils'; -import { createHistoricalPrebuiltRuleAssetSavedObjects, - createPrebuiltRuleAssetSavedObjects, - createRuleAssetSavedObject, -} from '../../utils/prebuilt_rules/create_prebuilt_rule_saved_objects'; -import { deleteAllPrebuiltRuleAssets } from '../../utils/prebuilt_rules/delete_all_prebuilt_rule_assets'; -import { installPrebuiltRulesAndTimelines } from '../../utils/prebuilt_rules/install_prebuilt_rules_and_timelines'; -import { installPrebuiltRules } from '../../utils/prebuilt_rules/install_prebuilt_rules'; -import { getPrebuiltRulesStatus } from '../../utils/prebuilt_rules/get_prebuilt_rules_status'; -import { upgradePrebuiltRules } from '../../utils/prebuilt_rules/upgrade_prebuilt_rules'; -import { getInstalledRules } from '../../utils/prebuilt_rules/get_installed_rules'; - -// eslint-disable-next-line import/no-default-export + getPrebuiltRulesStatus, + installPrebuiltRules, + getInstalledRules, + upgradePrebuiltRules, +} from '../../../utils'; + export default ({ getService }: FtrProviderContext): void => { const es = getService('es'); const supertest = getService('supertest'); const log = getService('log'); - describe('install and upgrade prebuilt rules with mock rule assets', () => { + describe('@ess @serverless @skipInQA install and upgrade prebuilt rules with mock rule assets', () => { beforeEach(async () => { await deleteAllRules(supertest, log); await deleteAllTimelines(es); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/update_prebuilt_rules_package/configs/ess.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/update_prebuilt_rules_package/configs/ess.config.ts new file mode 100644 index 00000000000000..0def0b0f17a5f3 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/update_prebuilt_rules_package/configs/ess.config.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrConfigProviderContext } from '@kbn/test'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const functionalConfig = await readConfigFile( + require.resolve('../../../../../../config/ess/config.base.trial') + ); + + return { + ...functionalConfig.getAll(), + testFiles: [require.resolve('..')], + junit: { + reportName: 'Detection Engine ESS / Update Prebuilt Rules Package - API Integration Tests', + }, + }; +} diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/update_prebuilt_rules_package/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/update_prebuilt_rules_package/configs/serverless.config.ts new file mode 100644 index 00000000000000..5f6716342c9246 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/update_prebuilt_rules_package/configs/serverless.config.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { createTestConfig } from '../../../../../../config/serverless/config.base'; + +export default createTestConfig({ + testFiles: [require.resolve('..')], + junit: { + reportName: + 'Detection Engine Serverless / Update Prebuilt Rules Package - API Integration Tests', + }, +}); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/update_prebuilt_rules_package/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/update_prebuilt_rules_package/index.ts new file mode 100644 index 00000000000000..48d0fb1f1fd991 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/update_prebuilt_rules_package/index.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../../../ftr_provider_context'; + +export default ({ loadTestFile }: FtrProviderContext): void => { + describe('Detection Engine API - Update Prebuilt Rules Package', function () { + loadTestFile(require.resolve('./update_prebuilt_rules_package')); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/update_prebuilt_rules_package/update_prebuilt_rules_package.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/update_prebuilt_rules_package/update_prebuilt_rules_package.ts similarity index 93% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/update_prebuilt_rules_package/update_prebuilt_rules_package.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/update_prebuilt_rules_package/update_prebuilt_rules_package.ts index 1d7939e83f9abe..981bfd71267800 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/update_prebuilt_rules_package/update_prebuilt_rules_package.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules/update_prebuilt_rules_package/update_prebuilt_rules_package.ts @@ -8,25 +8,23 @@ import fs from 'fs/promises'; import path from 'path'; import getMajorVersion from 'semver/functions/major'; import getMinorVersion from 'semver/functions/minor'; -// @ts-expect-error we have to check types with "allowJs: false" for now, causing this import to fail import { REPO_ROOT } from '@kbn/repo-info'; import JSON5 from 'json5'; import expect from 'expect'; import { PackageSpecManifest } from '@kbn/fleet-plugin/common'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { FtrProviderContext } from '../../../../../ftr_provider_context'; import { deleteAllPrebuiltRuleAssets, deleteAllRules, + getInstalledRules, getPrebuiltRulesStatus, installPrebuiltRules, + installPrebuiltRulesPackageByVersion, upgradePrebuiltRules, -} from '../../utils'; -import { reviewPrebuiltRulesToInstall } from '../../utils/prebuilt_rules/review_install_prebuilt_rules'; -import { reviewPrebuiltRulesToUpgrade } from '../../utils/prebuilt_rules/review_upgrade_prebuilt_rules'; -import { installPrebuiltRulesPackageByVersion } from '../../utils/prebuilt_rules/install_fleet_package_by_url'; -import { getInstalledRules } from '../../utils/prebuilt_rules/get_installed_rules'; + reviewPrebuiltRulesToInstall, + reviewPrebuiltRulesToUpgrade, +} from '../../../utils'; -// eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const es = getService('es'); const supertest = getService('supertest'); @@ -63,7 +61,7 @@ export default ({ getService }: FtrProviderContext): void => { return getPackageResponse.body.item.version ?? ''; }; - describe('update_prebuilt_rules_package', () => { + describe('@ess @serverless @skipInQA update_prebuilt_rules_package', () => { before(async () => { const configFilePath = path.resolve(REPO_ROOT, 'fleet_packages.json'); const fleetPackages = await fs.readFile(configFilePath, 'utf8'); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_creation/create_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_creation/create_rules.ts index 97b602c4db6175..5ace976f1f4fc8 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_creation/create_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_creation/create_rules.ts @@ -467,7 +467,8 @@ export default ({ getService }: FtrProviderContext) => { expect(body).to.eql({ error: 'Bad Request', - message: '[request body]: Invalid input', + message: + '[request body]: type: Invalid literal value, expected "eql", language: Invalid literal value, expected "eql", type: Invalid literal value, expected "query", type: Invalid literal value, expected "saved_query", saved_id: Required, and 14 more', statusCode: 400, }); }); @@ -574,7 +575,9 @@ export default ({ getService }: FtrProviderContext) => { .send(rule) .expect(400); - expect(body.message).to.eql('[request body]: Invalid input'); + expect(body.message).to.eql( + '[request body]: investigation_fields: Expected object, received array, type: Invalid literal value, expected "eql", language: Invalid literal value, expected "eql", investigation_fields: Expected object, received array, investigation_fields: Expected object, received array, and 22 more' + ); }); }); }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/get_alerts_by_ids.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/get_alerts_by_ids.ts index ce2b7ed1a4cf4f..1cfc922d766778 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/get_alerts_by_ids.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/get_alerts_by_ids.ts @@ -14,6 +14,7 @@ import type { RiskEnrichmentFields } from '@kbn/security-solution-plugin/server/ import { DETECTION_ENGINE_QUERY_SIGNALS_URL as DETECTION_ENGINE_QUERY_ALERTS_URL } from '@kbn/security-solution-plugin/common/constants'; import { countDownTest } from '../count_down_test'; import { getQueryAlertsId } from './get_query_alerts_ids'; +import { routeWithNamespace } from '../route_with_namespace'; /** * Given an array of rule ids this will return only alerts based on that rule id both @@ -25,12 +26,14 @@ export const getAlertsByIds = async ( supertest: SuperTest.SuperTest, log: ToolingLog, ids: string[], - size?: number + size?: number, + namespace?: string ): Promise> => { const alertsOpen = await countDownTest>( async () => { + const route = routeWithNamespace(DETECTION_ENGINE_QUERY_ALERTS_URL, namespace); const response = await supertest - .post(DETECTION_ENGINE_QUERY_ALERTS_URL) + .post(route) .set('kbn-xsrf', 'true') .send(getQueryAlertsId(ids, size)); if (response.status !== 200) { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/wait_for_alerts_to_be_present.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/wait_for_alerts_to_be_present.ts index e638bacf738c21..f7e873f98d4c72 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/wait_for_alerts_to_be_present.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/wait_for_alerts_to_be_present.ts @@ -21,11 +21,12 @@ export const waitForAlertsToBePresent = async ( supertest: SuperTest.SuperTest, log: ToolingLog, numberOfAlerts = 1, - alertIds: string[] + alertIds: string[], + namespace?: string ): Promise => { await waitFor( async () => { - const alertsOpen = await getAlertsByIds(supertest, log, alertIds, numberOfAlerts); + const alertsOpen = await getAlertsByIds(supertest, log, alertIds, numberOfAlerts, namespace); return alertsOpen.hits.hits.length >= numberOfAlerts; }, 'waitForAlertsToBePresent', diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/data_generator/README.md b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/data_generator/README.md new file mode 100644 index 00000000000000..e737e7b1339294 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/data_generator/README.md @@ -0,0 +1,606 @@ +# Data Generator for functional tests + +Helper to generate and index documents for using in Kibana functional tests + +- [Data Generator for functional tests](#data-generator-for-functional-tests) + - [DataGenerator](#datagenerator) + - [Initialization](#initialization) + - [Prerequisites](#prerequisites) + - [dataGeneratorFactory](#datageneratorfactory) + - [methods](#methods) + - [**indexListOfDocuments**](#indexlistofdocuments) + - [**indexGeneratedDocuments**](#indexgenerateddocuments) + - [**indexEnhancedDocuments**](#indexenhanceddocuments) + - [Utils](#utils) + - [**generateDocuments**](#generatedocuments) + - [**enhanceDocument**](#enhancedocument) + - [**enhanceDocuments**](#enhancedocuments) + - [Usage](#usage) + - [create test query rule that queries indexed documents within a test](#create-test-query-rule-that-queries-indexed-documents-within-a-test) + +## DataGenerator + +### Initialization + + +#### Prerequisites +1. Create index mappings in `x-pack/test/functional/es_archives/security_solution` + - create folder for index `foo_bar` + - add mappings file `mappings.json` in it + +
    + x-pack/test/functional/es_archives/security_solution/foo_bar/mappings.json + + ```JSON + { + "type": "index", + "value": { + "index": "foo_bar", + "mappings": { + "properties": { + "id": { + "type": "keyword" + }, + "@timestamp": { + "type": "date" + }, + "foo": { + "type": "keyword" + }, + } + }, + "settings": { + "index": { + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } + } + ``` +
    +2. Add in `before` of the test file index initialization + + ```ts + const esArchiver = getService('esArchiver'); + + before(async () => { + await esArchiver.load( + 'x-pack/test/functional/es_archives/security_solution/foo_bar' + ); + }); + + ``` + +3. Add in `after` of the test file index removal + + ```ts + const esArchiver = getService('esArchiver'); + + before(async () => { + await esArchiver.unload( + 'x-pack/test/functional/es_archives/security_solution/foo_bar' + ); + }); + + ``` + +#### dataGeneratorFactory + +`DataGeneratorParams` + +| Property | Description | Type | +| --------------- | ------------------------------------------------------ | ------ | +| es | ES client | `ESClient` | +| index | index where document will be added | `string` | +| log | log client | `LogClient`| + +1. import and initialize factory + + ```ts + import { dataGeneratorFactory } from '../../utils/data_generator'; + + const es = getService('es'); + const log = getService('log'); + + const { indexListOfDocuments, indexGeneratedDocuments } = dataGeneratorFactory({ + es, + index: 'foo_bar', + log, + }); + + ``` +2. Factory will return 2 methods which can be used to index documents into `foo_bar` + +where `getService` is method from `FtrProviderContext` + +### methods + +#### **indexListOfDocuments** + +| Property | Description | Type | +| --------------- | ------------------------------------------------------ | ------ | +| documents | list of documents to index | `Record` | + +Will index list of documents to `foo_bar` index as defined in `dataGeneratorFactory` params + +```ts + await indexListOfDocuments([{ foo: "bar" }, { id: "test-1" }]) + +``` + +#### **indexGeneratedDocuments** + +Will generate 10 documents in defined interval and index them in `foo_bar` index as defined in `dataGeneratorFactory` params +Method receives same parameters as [generateDocuments](#generateDocuments) util. + +```ts + await indexGeneratedDocuments({ + docsCount: 10, + interval: ['2020-10-28T07:30:00.000Z', '2020-10-30T07:30:00.000Z'], + seed: (i, id, timestamp) => ({ id, '@timestamp': timestamp, seq: i }) + }) + +``` + +#### **indexEnhancedDocuments** + +Will index list of enhanced documents to `foo_bar` index as defined in `dataGeneratorFactory` params +Method receives same parameters as [enhanceDocuments](#enhanceDocuments) util. + +```ts + await indexEnhancedDocuments({ + interval: ['1996-02-15T13:02:37.531Z', '2000-02-15T13:02:37.531Z'], + documents: [{ foo: 'bar' }, { foo: 'bar-1' }, { foo: 'bar-2' }] + }) + +``` + +## Utils + +### **generateDocuments** + +Util `generateDocuments` can generate list of documents based on basic seed function + + Seed callback will receive sequential number of document of document, generated id, timestamp. + Can be used to generate custom document with large set of options depends on needs. See examples below. + + | Property | Description | Type | + | --------------- | ------------------------------------------------------ | ------ | + | docsCount | number of documents to generate | `number` | + | seed | function that receives sequential number of document, generated id, timestamp as arguments and can used it create a document | `(index: number, id: string, timestamp: string) => Document` | + | interval | interval in which generate documents, defined by '@timestamp' field | `[string \| Date string \| Date]` _(optional)_ | + +Examples: + + 1. Generate 10 documents with random id, timestamp in interval between '2020-10-28T07:30:00.000Z', '2020-10-30T07:30:00.000Z', and field `seq` that represents sequential number of document + + ```ts + + const documents = generateDocuments({ + docsCount: 10, + interval: ['2020-10-28T07:30:00.000Z', '2020-10-30T07:30:00.000Z'], + seed: (i, id, timestamp) => ({ id, '@timestamp': timestamp, seq: i }) + }) + ``` + +
    +Generated docs + + ```JSON + [ + { + "id": "87d3d231-13c8-4d03-9ae4-d40781b3b2d1", + "@timestamp": "2020-10-30T04:00:55.790Z", + "seq": 0 + }, + { + "id": "90b99797-d0da-460d-86fd-eca40bedff39", + "@timestamp": "2020-10-28T08:43:01.117Z", + "seq": 1 + }, + { + "id": "809c05be-f401-4e31-86e1-55be8af4fac4", + "@timestamp": "2020-10-29T15:06:23.054Z", + "seq": 2 + }, + { + "id": "a2720f82-5401-4eab-b2eb-444a8425c937", + "@timestamp": "2020-10-29T23:19:47.790Z", + "seq": 3 + }, + { + "id": "e36e4418-4e89-4388-97df-97085b3fca92", + "@timestamp": "2020-10-29T09:14:00.966Z", + "seq": 4 + }, + { + "id": "4747adb3-0603-4651-8c0f-0c7df037f779", + "@timestamp": "2020-10-28T14:23:50.500Z", + "seq": 5 + }, + { + "id": "1fbfd873-b0ca-4cda-9c96-9a044622e712", + "@timestamp": "2020-10-28T10:00:20.995Z", + "seq": 6 + }, + { + "id": "9173cf93-1f9f-4f91-be5e-1e6888cb3aae", + "@timestamp": "2020-10-28T08:52:27.830Z", + "seq": 7 + }, + { + "id": "53245337-e383-4b28-9975-acbd79901b7c", + "@timestamp": "2020-10-29T08:58:02.385Z", + "seq": 8 + }, + { + "id": "0c700d33-df10-426e-8f71-677f437923ec", + "@timestamp": "2020-10-29T16:33:10.240Z", + "seq": 9 + } + ] + ``` + +
    + + 2. Generate 3 identical documents `{foo: bar}` + + ```ts + + const documents = generateDocuments({ + docsCount: 3, + seed: () => ({ foo: 'bar' }) + }) + ``` + +
    +Generated docs + + ```JSON + [ + { + "foo": "bar" + }, + { + "foo": "bar" + }, + { + "foo": "bar" + } + ] + ``` + +
    + + 3. Generate 5 documents with custom ingested timestamp, with no interval. If interval not defined, timestamp will be current time + + ```ts + + const documents = generateDocuments({ + docsCount: 5, + seed: (i, id, timestamp) => ({ foo: 'bar', event: { ingested: timestamp } }) + }) + ``` + +
    +Generated docs + + ```JSON + [ + { + "foo": "bar", + "event": { + "ingested": "2023-02-15T13:02:37.531Z" + } + }, + { + "foo": "bar", + "event": { + "ingested": "2023-02-15T13:02:37.531Z" + } + }, + { + "foo": "bar", + "event": { + "ingested": "2023-02-15T13:02:37.531Z" + } + }, + { + "foo": "bar", + "event": { + "ingested": "2023-02-15T13:02:37.531Z" + } + }, + { + "foo": "bar", + "event": { + "ingested": "2023-02-15T13:02:37.531Z" + } + } + ] + ``` + +
    + + 4. Generate 4 documents with custom if based on sequential number id + + ```ts + + const documents = generateDocuments({ + docsCount: 4, + seed: (i) => ({ foo: 'bar', id: `id-${i}`}) + }) + ``` + +
    +Generated docs + + ```JSON + [ + { + "foo": "bar", + "id": "id-0" + }, + { + "foo": "bar", + "id": "id-1" + }, + { + "foo": "bar", + "id": "id-2" + }, + { + "foo": "bar", + "id": "id-3" + } + ] + ``` + +
    + + +### **enhanceDocument** + +Adds generated `uuidv4` id and current time as `@timestamp` to document if `id`, `timestamp` params are not specified + + +`EnhanceDocumentOptions` + +| Property | Description | Type | +| --------------- | ------------------------------------------------------ | ------ | +| id | id for document | `string` _(optional)_ | +| timestamp | timestamp for document | `string` _(optional)_ | +| document | document to enhance | `Record` | + +Examples: + +1. Enhance document with generated `uuidv4` id and current time as `@timestamp` + + ```ts + const document = enhanceDocument({ + document: { foo: 'bar' }, + }); + ``` +
    + document + + ```JSON + { + "foo": "bar", + "id": "b501a64f-0dd4-4275-a38c-889be6a15a4d", + "@timestamp": "2023-02-15T17:21:21.429Z" + } + ``` + +
    + +2. Enhance document with generated `uuidv4` id and predefined timestamp + + + ```ts + const document = enhanceDocument({ + timestamp: '1996-02-15T13:02:37.531Z', + document: { foo: 'bar' }, + }); + ``` +
    + document + + ```JSON + { + "foo": "bar", + "id": "7b7460bf-e173-4744-af15-2c01ac52963b", + "@timestamp": "1996-02-15T13:02:37.531Z" + } + ``` + +
    + +3. Enhance document with predefined id and and current time as `@timestamp` + + + ```ts + const document = enhanceDocument({ + id: 'test-id', + document: { foo: 'bar' }, + }); + ``` +
    + document + + ```JSON + { + "foo": "bar", + "id": "test-id", + "@timestamp": "2023-02-15T17:21:21.429Z" + } + ``` +
    + +### **enhanceDocuments** + + + +Adds generated `uuidv4` `id` property to list of documents if `id` parameter is not specified. +Adds `@timestamp` in defined interval to list of documents. If it's not specified, `@timestamp` will be added as current time + +| Property | Description | Type | +| --------------- | ------------------------------------------------------ | ------ | +| documents | documents to enhance | `Record[]` | +| id | id for documents | `string` _(optional)_ | +| interval | interval in which generate documents, defined by '@timestamp' field | `[string \| Date string \| Date]` _(optional)_ | + +Examples: + +1. Enhance documents with generated `uuidv4` id and current time as `@timestamp` + + ```ts + const documents = enhanceDocuments({ + documents: [{ foo: 'bar' }, { foo: 'bar-1' }, { foo: 'bar-2' }] + }); + ``` +
    + documents + + ```JSON + [ + { + "foo": "bar", + "id": "c55ddd6b-3cf2-4ebf-94d6-4eeeb4e5b655", + "@timestamp": "2023-02-16T16:43:13.573Z" + }, + { + "foo": "bar-1", + "id": "61b157b9-5f1f-4d99-a5bf-072069f5139d", + "@timestamp": "2023-02-16T16:43:13.573Z" + }, + { + "foo": "bar-2", + "id": "04929927-6d9e-4ccc-b083-250e3fe2d7a7", + "@timestamp": "2023-02-16T16:43:13.573Z" + } + ] + ``` + +
    + +2. Enhance document with generated `uuidv4` id and timestamp in predefined interval + + ```ts + const documents = enhanceDocuments({ + interval: ['1996-02-15T13:02:37.531Z', '2000-02-15T13:02:37.531Z'], + documents: [{ foo: 'bar' }, { foo: 'bar-1' }, { foo: 'bar-2' }] + }); + ``` +
    + documents + + ```JSON + [ + { + "foo": "bar", + "id": "883a67cb-0a57-4711-bdf9-e8a394a52460", + "@timestamp": "1998-07-04T15:16:46.587Z" + }, + { + "foo": "bar-1", + "id": "70691d9e-1030-412f-8ae1-c6db50e90e91", + "@timestamp": "1998-05-15T07:00:52.339Z" + }, + { + "foo": "bar-2", + "id": "b2140328-5cc4-4532-947e-30b8fd830ed7", + "@timestamp": "1999-09-01T21:50:38.957Z" + } + ] + ``` + +
    + +3. Enhance documents with predefined id and and current time as `@timestamp` + + ```ts + const documents = enhanceDocuments({ + id: 'test-id', + documents: [{ foo: 'bar' }, { foo: 'bar-1' }, { foo: 'bar-2' }] + }); + ``` +
    + documents + + ```JSON + [ + { + "foo": "bar", + "id": "test-id", + "@timestamp": "2023-02-16T16:43:13.574Z" + }, + { + "foo": "bar-1", + "id": "test-id", + "@timestamp": "2023-02-16T16:43:13.574Z" + }, + { + "foo": "bar-2", + "id": "test-id", + "@timestamp": "2023-02-16T16:43:13.574Z" + } + ] + + ``` +
    + +## Usage + +### create test query rule that queries indexed documents within a test + +When documents generated and indexed, there might be a need to create a test rule that targets only these documents. So, documents generated in the test, will be used only in context of this test. + +There are few possible ways to do this + +1. Create new index every time for a new test. Thus, newly indexed documents, will be the only documents present in test index. It might be costly operation, as it will require to create new index for each test, that re-initialize dataGeneratorFactory, or delete index after rule's run + +2. Use the same id or specific field in documents. + For example: + + ```ts + + const id = uuidv4(); + const firstTimestamp = new Date().toISOString(); + const firstDocument = { + id, + '@timestamp': firstTimestamp, + agent: { + name: 'agent-1', + }, + }; + await indexListOfDocuments([firstDocument, firstDocument]); + + const rule: QueryRuleCreateProps = { + ...getRuleForSignalTesting(['ecs_compliant']), + query: `id:${id}`, + }; + + + ``` + + All documents will have the same `id` and can be queried by following `id:${id}` + +3. Use utility method `getKQLQueryFromDocumentList` that will create query from all ids in generated documents + + ```ts + const { documents } = await indexGeneratedDocuments({ + docsCount: 4, + document: { foo: 'bar' }, + enhance: true, + }); + + const query = getKQLQueryFromDocumentList(documents); + const rule = { + ...getRuleForSignalTesting(['ecs_non_compliant']), + query, + }; + ``` + + util will generate the following query: `(id: "f6ca3ee1-407c-4685-a94b-11ef4ed5136b" or id: "2a7358b2-8cad-47ce-83b7-e4418c266f3e" or id: "9daec569-0ba1-4c46-a0c6-e340cee1c5fb" or id: "b03c2fdf-0ca1-447c-b8c6-2cc5a663ffe2")`, that will include all generated documents \ No newline at end of file diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/data_generator/data_generator_factory.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/data_generator/data_generator_factory.ts new file mode 100644 index 00000000000000..7842d105e40b03 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/data_generator/data_generator_factory.ts @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Client } from '@elastic/elasticsearch'; +import { ToolingLog } from '@kbn/tooling-log'; +import type { BulkResponse } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { indexDocuments } from './index_documents'; +import { generateDocuments } from './generate_documents'; +import { enhanceDocuments, EnhanceDocumentsOptions } from './enhance_documents'; +import type { GenerateDocumentsParams } from './generate_documents'; +import type { Document } from './types'; + +interface DataGeneratorParams { + es: Client; + documents: Array>; + index: string; + log: ToolingLog; +} + +interface DataGeneratorResponse { + response: BulkResponse; + documents: Document[]; +} + +interface DataGenerator { + indexListOfDocuments: (docs: Document[]) => Promise; + indexGeneratedDocuments: (params: GenerateDocumentsParams) => Promise; + indexEnhancedDocuments: (params: EnhanceDocumentsOptions) => Promise; +} + +/** + * initialize {@link DataGenerator} + * @param param.es - ES client + * @param params.index - index where document will be added + * @param params.log - logClient + * @returns methods of {@link DataGenerator} + */ +export const dataGeneratorFactory = ({ + es, + index, + log, +}: Omit): DataGenerator => { + return { + indexListOfDocuments: async (documents: DataGeneratorParams['documents']) => { + const response = await indexDocuments({ es, index, documents, log }); + + return { + documents, + response, + }; + }, + indexGeneratedDocuments: async (params: GenerateDocumentsParams) => { + const documents = generateDocuments(params); + const response = await indexDocuments({ es, index, documents, log }); + + return { + documents, + response, + }; + }, + indexEnhancedDocuments: async (params: EnhanceDocumentsOptions) => { + const documents = enhanceDocuments(params); + const response = await indexDocuments({ es, index, documents, log }); + + return { + documents, + response, + }; + }, + }; +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/data_generator/enhance_document.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/data_generator/enhance_document.ts new file mode 100644 index 00000000000000..f2e244edb90b39 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/data_generator/enhance_document.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { v4 as uuidv4 } from 'uuid'; + +interface EnhanceDocumentOptions { + id?: string; + timestamp?: string; + document: Record; +} + +/** + * enhances document with generated id and timestamp + * @param {string} options.id - optional id, if not provided randomly generated + * @param {string} options.timestamp - optional timestamp of document, if not provided current time + * @param {Record} options.document - document that will be enhanced + */ +export const enhanceDocument = (options: EnhanceDocumentOptions) => { + const id = options?.id ?? uuidv4(); + const timestamp = options?.timestamp ?? new Date().toISOString(); + return { + ...options.document, + id, + '@timestamp': timestamp, + }; +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/data_generator/enhance_documents.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/data_generator/enhance_documents.ts new file mode 100644 index 00000000000000..5d701afe166ed6 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/data_generator/enhance_documents.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { IndexingInterval, Document } from './types'; +import { getTimestamp } from './get_timestamp'; +import { enhanceDocument } from './enhance_document'; + +export interface EnhanceDocumentsOptions { + interval?: IndexingInterval; + documents: Document[]; + id?: string; +} + +/** + * enhances documents with generated id and timestamp within interval + * @param {string} options.id - optional id, if not provided randomly generated + * @param {string} options.interval - optional interval of document, if not provided set as a current time + * @param {Record[]} options.documents - documents that will be enhanced + */ +export const enhanceDocuments = ({ documents, interval, id }: EnhanceDocumentsOptions) => { + return documents.map((document) => + enhanceDocument({ + document, + id, + timestamp: getTimestamp(interval), + }) + ); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/data_generator/generate_documents.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/data_generator/generate_documents.ts new file mode 100644 index 00000000000000..c9ba960867a43e --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/data_generator/generate_documents.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { v4 as uuidv4 } from 'uuid'; +import { getTimestamp } from './get_timestamp'; + +import type { Document, IndexingInterval } from './types'; + +type DocumentSeedFunc = (index: number, id: string, timestamp: string) => Document; + +export interface GenerateDocumentsParams { + interval?: IndexingInterval; + docsCount: number; + seed: DocumentSeedFunc; +} + +/** + * + * @param param.interval - interval in which generate documents, defined by '@timestamp' field + * @param param.docsCount - number of document to generate + * @param param.seed - seed function. Function that receives index of document, generated id, timestamp as arguments and can used it create a document + * @returns generated Documents + */ +export const generateDocuments = ({ docsCount, interval, seed }: GenerateDocumentsParams) => { + const documents = []; + + for (let i = 0; i < docsCount; i++) { + const id = uuidv4(); + const timestamp = getTimestamp(interval); + + documents.push(seed(i, id, timestamp)); + } + + return documents; +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/data_generator/get_kql_query_from_documents_list.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/data_generator/get_kql_query_from_documents_list.ts new file mode 100644 index 00000000000000..3347c2120d4a42 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/data_generator/get_kql_query_from_documents_list.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 type { Document } from './types'; + +/** + * returns KQL query from a list documents that includes all documents by their ids. + * it can be used later to create test rules that will query only these documents + * ```ts + * const documents = [ + { + foo: 'bar', + id: 'f07df596-65ec-4ab1-b0b2-f3b69558ed26', + '@timestamp': '2020-10-29T07:10:51.989Z', + }, + { + foo: 'bar', + id: 'e07614f9-1dc5-4849-90c4-31362bbdf8d0', + '@timestamp': '2020-10-30T00:32:48.987Z', + }, + { + foo: 'test', + id: 'e03a5b12-77e6-4aa3-b0be-fbe5b0843f07', + '@timestamp': '2020-10-29T03:40:35.318Z', + }, + ]; + + const query = getKQLQueryFromDocumentList(documents); + + // query equals to + // (id: "f07df596-65ec-4ab1-b0b2-f3b69558ed26" or id: "e07614f9-1dc5-4849-90c4-31362bbdf8d0" or id: "e03a5b12-77e6-4aa3-b0be-fbe5b0843f07") + * ``` + */ +export const getKQLQueryFromDocumentList = (documents: Document[]) => { + const orClauses = documents.map(({ id }) => `id: "${id}"`).join(' or '); + + return `(${orClauses})`; +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/data_generator/get_timestamp.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/data_generator/get_timestamp.ts new file mode 100644 index 00000000000000..e828767a396502 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/data_generator/get_timestamp.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 faker from 'faker'; +import type { IndexingInterval } from './types'; + +export const getTimestamp = (interval?: IndexingInterval) => { + if (interval) { + return faker.date.between(...interval).toISOString(); + } + + return new Date().toISOString(); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/data_generator/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/data_generator/index.ts new file mode 100644 index 00000000000000..4cd56dd896554d --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/data_generator/index.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './data_generator_factory'; +export * from './enhance_document'; +export * from './enhance_documents'; +export * from './generate_documents'; +export * from './get_kql_query_from_documents_list'; +export * from './get_timestamp'; +export * from './index_documents'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/data_generator/index_documents.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/data_generator/index_documents.ts new file mode 100644 index 00000000000000..5408e11b250158 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/data_generator/index_documents.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Client } from '@elastic/elasticsearch'; +import type { BulkResponse } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { ToolingLog } from '@kbn/tooling-log'; + +interface IndexDocumentsParams { + es: Client; + documents: Array>; + index: string; + log: ToolingLog; +} + +type IndexDocuments = (params: IndexDocumentsParams) => Promise; + +/** + * Indexes documents into provided index + */ +export const indexDocuments: IndexDocuments = async ({ es, documents, index, log }) => { + const operations = documents.flatMap((doc: object) => [{ index: { _index: index } }, doc]); + + const response = await es.bulk({ refresh: true, operations }); + + // throw error if document wasn't indexed, so test will be terminated earlier and no false positives can happen + response.items.some(({ index: responseIndex } = {}) => { + if (responseIndex?.error) { + log.error( + `Failed to index document in non_ecs_fields test suits: "${responseIndex.error?.reason}"` + ); + throw Error(responseIndex.error.message); + } + }); + return response; +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/data_generator/types.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/data_generator/types.ts new file mode 100644 index 00000000000000..bbf54be68cfee6 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/data_generator/types.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export type IndexingInterval = [string | Date, string | Date]; + +export type Document = Record; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/index.ts index 56d166c501b6b9..9af5821d08ac37 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/index.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/index.ts @@ -8,6 +8,8 @@ export * from './rules'; export * from './exception_list_and_item'; export * from './alerts'; export * from './actions'; +export * from './data_generator'; + export * from './rules/get_rule_so_by_id'; export * from './rules/create_rule_saved_object'; export * from './rules/get_rule_with_legacy_investigation_fields'; @@ -18,4 +20,5 @@ export * from './count_down_es'; export * from './update_username'; export * from './refresh_index'; export * from './wait_for'; +export * from './route_with_namespace'; export * from './wait_for_index_to_populate'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/delete_all_prebuilt_rule_assets.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/delete_all_prebuilt_rule_assets.ts new file mode 100644 index 00000000000000..899d5ddd7f83f1 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/delete_all_prebuilt_rule_assets.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Client } from '@elastic/elasticsearch'; +import { SECURITY_SOLUTION_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; + +/** + * Remove all prebuilt rule assets from the security solution savedObjects index + * @param es The ElasticSearch handle + */ +export const deleteAllPrebuiltRuleAssets = async (es: Client): Promise => { + await es.deleteByQuery({ + index: SECURITY_SOLUTION_SAVED_OBJECT_INDEX, + q: 'type:security-rule', + wait_for_completion: true, + refresh: true, + body: {}, + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/delete_all_timelines.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/delete_all_timelines.ts new file mode 100644 index 00000000000000..291cd269580b07 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/delete_all_timelines.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Client } from '@elastic/elasticsearch'; +import { SECURITY_SOLUTION_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; + +/** + * Remove all timelines from the security solution savedObjects index + * @param es The ElasticSearch handle + */ +export const deleteAllTimelines = async (es: Client): Promise => { + await es.deleteByQuery({ + index: SECURITY_SOLUTION_SAVED_OBJECT_INDEX, + q: 'type:siem-ui-timeline', + wait_for_completion: true, + refresh: true, + body: {}, + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/delete_prebuilt_rules_fleet_package.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/delete_prebuilt_rules_fleet_package.ts similarity index 84% rename from x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/delete_prebuilt_rules_fleet_package.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/delete_prebuilt_rules_fleet_package.ts index 1bb596e2baeb12..1647ff301a3246 100644 --- a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/delete_prebuilt_rules_fleet_package.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/delete_prebuilt_rules_fleet_package.ts @@ -18,10 +18,11 @@ export async function deletePrebuiltRulesFleetPackage( ) { const resp = await supertest .get(epmRouteService.getInfoPath('security_detection_engine')) - .send() - .expect(200); + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .send(); - if (resp.body.response.status === 'installed') { + if (resp.status === 200 && resp.body.response.status === 'installed') { await supertest .delete( epmRouteService.getRemovePath('security_detection_engine', resp.body.response.version) diff --git a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/get_installed_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/get_installed_rules.ts similarity index 100% rename from x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/get_installed_rules.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/get_installed_rules.ts diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/get_prebuilt_rules_fleet_package.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/get_prebuilt_rules_fleet_package.ts new file mode 100644 index 00000000000000..ec69b6cfb14c28 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/get_prebuilt_rules_fleet_package.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { epmRouteService } from '@kbn/fleet-plugin/common'; +import type SuperTest from 'supertest'; + +/** + * Gets the security_detection_engine package using fleet API. + * + * @param supertest Supertest instance + * @returns The API endpoint response. Will have status 200 if package installed or 404 if not + */ +export async function getPrebuiltRulesFleetPackage(supertest: SuperTest.SuperTest) { + return await supertest + .get(epmRouteService.getInfoPath('security_detection_engine')) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .send(); +} diff --git a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/get_prebuilt_rules_status.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/get_prebuilt_rules_status.ts similarity index 95% rename from x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/get_prebuilt_rules_status.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/get_prebuilt_rules_status.ts index db7d8553ad9466..0f785f8a774538 100644 --- a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/get_prebuilt_rules_status.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/get_prebuilt_rules_status.ts @@ -23,6 +23,7 @@ export const getPrebuiltRulesStatus = async ( .get(GET_PREBUILT_RULES_STATUS_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '1') + .set('x-elastic-internal-origin', 'foo') .send() .expect(200); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/index.ts index 9970b6b13eeec5..fbf9ab7b36384e 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/index.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/index.ts @@ -5,6 +5,18 @@ * 2.0. */ export * from './create_prebuilt_rule_saved_objects'; +export * from './delete_all_prebuilt_rule_assets'; +export * from './delete_all_timelines'; +export * from './delete_prebuilt_rules_fleet_package'; +export * from './get_installed_rules'; export * from './get_prebuilt_rules_and_timelines_status'; +export * from './get_prebuilt_rules_status'; +export * from './get_prebuilt_rules_fleet_package'; +export * from './install_fleet_package_by_url'; export * from './install_mock_prebuilt_rules'; export * from './install_prebuilt_rules_and_timelines'; +export * from './install_prebuilt_rules_fleet_package'; +export * from './install_prebuilt_rules'; +export * from './review_install_prebuilt_rules'; +export * from './review_upgrade_prebuilt_rules'; +export * from './upgrade_prebuilt_rules'; diff --git a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_fleet_package_by_url.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/install_fleet_package_by_url.ts similarity index 93% rename from x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_fleet_package_by_url.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/install_fleet_package_by_url.ts index bccdf28906e23d..259369346cc8b9 100644 --- a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_fleet_package_by_url.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/install_fleet_package_by_url.ts @@ -8,6 +8,7 @@ import type { Client } from '@elastic/elasticsearch'; import type SuperTest from 'supertest'; import { ALL_SAVED_OBJECT_INDICES } from '@kbn/core-saved-objects-server'; import { InstallPackageResponse } from '@kbn/fleet-plugin/common/types'; +import { epmRouteService } from '@kbn/fleet-plugin/common'; /** * Installs latest available non-prerelease prebuilt rules package `security_detection_engine`. @@ -25,6 +26,7 @@ export const installPrebuiltRulesPackageViaFleetAPI = async ( const fleetResponse = await supertest .post(`/api/fleet/epm/packages/security_detection_engine`) .set('kbn-xsrf', 'xxxx') + .set('elastic-api-version', '2023-10-31') .type('application/json') .send({ force: true }) .expect(200); @@ -63,8 +65,9 @@ export const installPrebuiltRulesPackageByVersion = async ( version: string ): Promise => { const fleetResponse = await supertest - .post(`/api/fleet/epm/packages/security_detection_engine/${version}`) + .post(epmRouteService.getInstallPath('security_detection_engine', version)) .set('kbn-xsrf', 'xxxx') + .set('elastic-api-version', '2023-10-31') .type('application/json') .send({ force: true }) .expect(200); diff --git a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_prebuilt_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/install_prebuilt_rules.ts similarity index 98% rename from x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_prebuilt_rules.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/install_prebuilt_rules.ts index 52e34d67a1936e..308fef271e9875 100644 --- a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_prebuilt_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/install_prebuilt_rules.ts @@ -43,6 +43,7 @@ export const installPrebuiltRules = async ( .post(PERFORM_RULE_INSTALLATION_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '1') + .set('x-elastic-internal-origin', 'foo') .send(payload) .expect(200); diff --git a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_prebuilt_rules_fleet_package.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/install_prebuilt_rules_fleet_package.ts similarity index 100% rename from x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_prebuilt_rules_fleet_package.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/install_prebuilt_rules_fleet_package.ts diff --git a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/review_install_prebuilt_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/review_install_prebuilt_rules.ts similarity index 95% rename from x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/review_install_prebuilt_rules.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/review_install_prebuilt_rules.ts index ff7987d776213c..573b481a3b30f7 100644 --- a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/review_install_prebuilt_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/review_install_prebuilt_rules.ts @@ -22,6 +22,7 @@ export const reviewPrebuiltRulesToInstall = async ( .post(REVIEW_RULE_INSTALLATION_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '1') + .set('x-elastic-internal-origin', 'securitySolution') .send() .expect(200); diff --git a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/review_upgrade_prebuilt_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/review_upgrade_prebuilt_rules.ts similarity index 94% rename from x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/review_upgrade_prebuilt_rules.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/review_upgrade_prebuilt_rules.ts index fdcdab5dd9ed34..9bbf980dccccae 100644 --- a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/review_upgrade_prebuilt_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/review_upgrade_prebuilt_rules.ts @@ -22,6 +22,7 @@ export const reviewPrebuiltRulesToUpgrade = async ( .post(REVIEW_RULE_UPGRADE_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '1') + .set('x-elastic-internal-origin', 'securitySolution') .send() .expect(200); diff --git a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/upgrade_prebuilt_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/upgrade_prebuilt_rules.ts similarity index 98% rename from x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/upgrade_prebuilt_rules.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/upgrade_prebuilt_rules.ts index 1fbbe46e1e7ff4..caadba2619a741 100644 --- a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/upgrade_prebuilt_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/upgrade_prebuilt_rules.ts @@ -39,6 +39,7 @@ export const upgradePrebuiltRules = async ( .post(PERFORM_RULE_UPGRADE_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '1') + .set('x-elastic-internal-origin', 'foo') .send(payload) .expect(200); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/config.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/configs/ess.config.ts similarity index 62% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/config.ts rename to x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/configs/ess.config.ts index 2430b8f2148d9a..bb4e078746d58a 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/configs/ess.config.ts @@ -6,13 +6,16 @@ */ import { FtrConfigProviderContext } from '@kbn/test'; - -// eslint-disable-next-line import/no-default-export export default async function ({ readConfigFile }: FtrConfigProviderContext) { - const functionalConfig = await readConfigFile(require.resolve('../config.base.ts')); + const functionalConfig = await readConfigFile( + require.resolve('../../../../../config/ess/config.base.trial') + ); return { ...functionalConfig.getAll(), - testFiles: [require.resolve('.')], + testFiles: [require.resolve('..')], + junit: { + reportName: 'Entity Analytics API Integration Tests - ESS - Risk Engine', + }, }; } diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/configs/serverless.config.ts new file mode 100644 index 00000000000000..e28df5b9c35060 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/configs/serverless.config.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 { createTestConfig } from '../../../../../config/serverless/config.base'; + +export default createTestConfig({ + testFiles: [require.resolve('..')], + junit: { + reportName: 'Entity Analytics API Integration Tests - Serverless - Risk Engine', + }, +}); diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/index.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/index.ts new file mode 100644 index 00000000000000..878725cd32f9a5 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/index.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('Entity Analytics - Risk Engine', function () { + loadTestFile(require.resolve('./init_and_status_apis')); + loadTestFile(require.resolve('./risk_score_calculation')); + loadTestFile(require.resolve('./risk_score_preview')); + loadTestFile(require.resolve('./risk_scoring_task/task_execution')); + loadTestFile(require.resolve('./risk_scoring_task/task_execution_nondefault_spaces')); + loadTestFile(require.resolve('./telemetry_usage')); + }); +} diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/risk_engine/init_and_status_apis.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/init_and_status_apis.ts similarity index 93% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/group10/risk_engine/init_and_status_apis.ts rename to x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/init_and_status_apis.ts index 480ebd9d9c8452..c88a30e8b42d84 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/risk_engine/init_and_status_apis.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/init_and_status_apis.ts @@ -6,23 +6,20 @@ */ import expect from '@kbn/expect'; -import { riskEngineConfigurationTypeName } from '@kbn/security-solution-plugin/server/lib/risk_engine/saved_object'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { riskEngineConfigurationTypeName } from '@kbn/security-solution-plugin/server/lib/entity_analytics/risk_engine/saved_object'; + import { - cleanRiskEngineConfig, legacyTransformIds, createLegacyTransforms, clearLegacyTransforms, riskEngineRouteHelpersFactory, - clearTransforms, installLegacyRiskScore, getLegacyRiskScoreDashboards, clearLegacyDashboards, - deleteRiskEngineTask, - deleteAllRiskScores, -} from './utils'; + cleanRiskEngine, +} from '../../utils'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; -// eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const es = getService('es'); const supertest = getService('supertest'); @@ -30,37 +27,17 @@ export default ({ getService }: FtrProviderContext) => { const riskEngineRoutes = riskEngineRouteHelpersFactory(supertest); const log = getService('log'); - describe('Risk Engine', () => { + describe('@ess @serverless init_and_status_apis', () => { beforeEach(async () => { - await cleanRiskEngineConfig({ kibanaServer }); - await deleteRiskEngineTask({ es, log }); - await deleteAllRiskScores(log, es); - await clearTransforms({ - es, - log, - }); + await cleanRiskEngine({ kibanaServer, es, log }); }); afterEach(async () => { - await cleanRiskEngineConfig({ - kibanaServer, - }); - await clearLegacyTransforms({ - es, - log, - }); - await clearTransforms({ - es, - log, - }); - await clearLegacyDashboards({ - supertest, - log, - }); - await deleteRiskEngineTask({ es, log }); + await cleanRiskEngine({ kibanaServer, es, log }); + await clearLegacyTransforms({ es, log }); + await clearLegacyDashboards({ supertest, log }); }); - // FLAKY: https://github.com/elastic/kibana/issues/168376 describe('init api', () => { it('should return response with success status', async () => { const response = await riskEngineRoutes.init(); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/risk_engine/risk_score_calculation.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/risk_score_calculation.ts similarity index 95% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/group10/risk_engine/risk_score_calculation.ts rename to x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/risk_score_calculation.ts index f03214e301dd14..74e37b4737f716 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/risk_engine/risk_score_calculation.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/risk_score_calculation.ts @@ -6,12 +6,16 @@ */ import expect from '@kbn/expect'; +import { X_ELASTIC_INTERNAL_ORIGIN_REQUEST } from '@kbn/core-http-common'; + import { RISK_SCORE_CALCULATION_URL } from '@kbn/security-solution-plugin/common/constants'; import type { RiskScore } from '@kbn/security-solution-plugin/common/risk_engine'; import { v4 as uuidv4 } from 'uuid'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; -import { deleteAllAlerts, deleteAllRules } from '../../../utils'; -import { dataGeneratorFactory } from '../../../utils/data_generator'; +import { + deleteAllAlerts, + deleteAllRules, + dataGeneratorFactory, +} from '../../../detections_response/utils'; import { buildDocument, createAndSyncRuleAndAlertsFactory, @@ -19,9 +23,9 @@ import { readRiskScores, normalizeScores, waitForRiskScoresToBePresent, -} from './utils'; +} from '../../utils'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; -// eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); @@ -39,6 +43,7 @@ export default ({ getService }: FtrProviderContext): void => { .post(RISK_SCORE_CALCULATION_URL) .set('kbn-xsrf', 'true') .set('elastic-api-version', '1') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .send(body) .expect(200); return result; @@ -63,7 +68,7 @@ export default ({ getService }: FtrProviderContext): void => { }); }; - describe('Risk Engine - Risk Scoring Calculation API', () => { + describe('@ess @serverless Risk Scoring Calculation API', () => { context('with auditbeat data', () => { const { indexListOfDocuments } = dataGeneratorFactory({ es, diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/risk_engine/risk_score_preview.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/risk_score_preview.ts similarity index 97% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/group10/risk_engine/risk_score_preview.ts rename to x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/risk_score_preview.ts index 31f8a86efc56eb..ae1fdb82d09532 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/risk_engine/risk_score_preview.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/risk_score_preview.ts @@ -10,17 +10,22 @@ import { ALERT_RISK_SCORE } from '@kbn/rule-data-utils'; import { RISK_SCORE_PREVIEW_URL } from '@kbn/security-solution-plugin/common/constants'; import type { RiskScore } from '@kbn/security-solution-plugin/common/risk_engine'; import { v4 as uuidv4 } from 'uuid'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; -import { createSignalsIndex, deleteAllAlerts, deleteAllRules } from '../../../utils'; -import { dataGeneratorFactory } from '../../../utils/data_generator'; +import { X_ELASTIC_INTERNAL_ORIGIN_REQUEST } from '@kbn/core-http-common'; +import { + createAlertsIndex, + deleteAllAlerts, + deleteAllRules, + dataGeneratorFactory, +} from '../../../detections_response/utils'; import { buildDocument, createAndSyncRuleAndAlertsFactory, deleteAllRiskScores, sanitizeScores, -} from './utils'; +} from '../../utils'; + +import { FtrProviderContext } from '../../../../ftr_provider_context'; -// eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); @@ -37,6 +42,7 @@ export default ({ getService }: FtrProviderContext): void => { const { body: result } = await supertest .post(RISK_SCORE_PREVIEW_URL) .set('elastic-api-version', '1') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .set('kbn-xsrf', 'true') .send({ ...defaultBody, ...body }) .expect(200); @@ -56,7 +62,7 @@ export default ({ getService }: FtrProviderContext): void => { return await previewRiskScores({ body: {} }); }; - describe('Risk Engine - Risk Scoring Preview API', () => { + describe('@ess @serverless Risk Scoring Preview API', () => { context('with auditbeat data', () => { const { indexListOfDocuments } = dataGeneratorFactory({ es, @@ -78,7 +84,7 @@ export default ({ getService }: FtrProviderContext): void => { await deleteAllAlerts(supertest, log, es); await deleteAllRules(supertest, log); - await createSignalsIndex(supertest, log); + await createAlertsIndex(supertest, log); }); afterEach(async () => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/risk_scoring_task/ftr_provider_context_with_spaces.d.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/risk_scoring_task/ftr_provider_context_with_spaces.d.ts new file mode 100644 index 00000000000000..922a5d9d25b715 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/risk_scoring_task/ftr_provider_context_with_spaces.d.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 { GenericFtrProviderContext } from '@kbn/test'; + +import { SpacesServiceProvider } from '../../../../../../common/services/spaces'; +import { services as serverlessServices } from '../../../../../../../test_serverless/api_integration/services'; + +const services = { + ...serverlessServices, + spaces: SpacesServiceProvider, +}; +export type FtrProviderContextWithSpaces = GenericFtrProviderContext; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/risk_engine/risk_scoring_task_execution.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/risk_scoring_task/task_execution.ts similarity index 67% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/group10/risk_engine/risk_scoring_task_execution.ts rename to x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/risk_scoring_task/task_execution.ts index 6a95d236a9d0aa..53c70bc90efb9e 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/risk_engine/risk_scoring_task_execution.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/risk_scoring_task/task_execution.ts @@ -7,27 +7,25 @@ import expect from '@kbn/expect'; import { v4 as uuidv4 } from 'uuid'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; -import { deleteAllAlerts, deleteAllRules } from '../../../utils'; -import { dataGeneratorFactory } from '../../../utils/data_generator'; +import { + deleteAllAlerts, + deleteAllRules, + dataGeneratorFactory, +} from '../../../../detections_response/utils'; import { buildDocument, createAndSyncRuleAndAlertsFactory, - deleteRiskEngineTask, - deleteAllRiskScores, readRiskScores, waitForRiskScoresToBePresent, normalizeScores, riskEngineRouteHelpersFactory, updateRiskEngineConfigSO, getRiskEngineTask, - cleanRiskEngineConfig, waitForRiskEngineTaskToBeGone, - deleteRiskScoreIndices, - clearTransforms, -} from './utils'; + cleanRiskEngine, +} from '../../../utils'; +import { FtrProviderContext } from '../../../../../ftr_provider_context'; -// eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); @@ -38,7 +36,7 @@ export default ({ getService }: FtrProviderContext): void => { const createAndSyncRuleAndAlerts = createAndSyncRuleAndAlertsFactory({ supertest, log }); const riskEngineRoutes = riskEngineRouteHelpersFactory(supertest); - describe('Risk Engine - Risk Scoring Task', () => { + describe('@ess @serverless Risk Scoring Task Execution', () => { context('with auditbeat data', () => { const { indexListOfDocuments } = dataGeneratorFactory({ es, @@ -57,21 +55,15 @@ export default ({ getService }: FtrProviderContext): void => { }); beforeEach(async () => { - await cleanRiskEngineConfig({ kibanaServer }); - await deleteRiskEngineTask({ es, log }); - await deleteAllRiskScores(log, es); + await cleanRiskEngine({ kibanaServer, es, log }); await deleteAllAlerts(supertest, log, es); await deleteAllRules(supertest, log); - await clearTransforms({ es, log }); }); afterEach(async () => { - await cleanRiskEngineConfig({ kibanaServer }); - await deleteRiskEngineTask({ es, log }); - await deleteAllRiskScores(log, es); + await cleanRiskEngine({ kibanaServer, es, log }); await deleteAllAlerts(supertest, log, es); await deleteAllRules(supertest, log); - await clearTransforms({ es, log }); }); describe('with some alerts containing hosts', () => { @@ -101,7 +93,7 @@ export default ({ getService }: FtrProviderContext): void => { await riskEngineRoutes.init(); }); - it('calculates and persists risk scores for alert documents', async () => { + it('@skipInQA calculates and persists risk scores for alert documents', async () => { await waitForRiskScoresToBePresent({ es, log, scoreCount: 10 }); const scores = await readRiskScores(es); @@ -112,7 +104,7 @@ export default ({ getService }: FtrProviderContext): void => { ); }); - it('starts the latest transform', async () => { + it('@skipInQA starts the latest transform', async () => { await waitForRiskScoresToBePresent({ es, log, scoreCount: 10 }); const transformStats = await es.transform.getTransformStats({ @@ -122,7 +114,7 @@ export default ({ getService }: FtrProviderContext): void => { expect(transformStats.transforms[0].state).to.eql('started'); }); - describe('disabling and re-enabling the risk engine', () => { + describe('@skipInQA disabling and re-enabling the risk engine', () => { beforeEach(async () => { await waitForRiskScoresToBePresent({ es, log, scoreCount: 10 }); await riskEngineRoutes.disable(); @@ -144,7 +136,7 @@ export default ({ getService }: FtrProviderContext): void => { }); }); - describe('disabling the risk engine', () => { + describe('@skipInQA disabling the risk engine', () => { beforeEach(async () => { await waitForRiskScoresToBePresent({ es, log, scoreCount: 10 }); }); @@ -223,7 +215,7 @@ export default ({ getService }: FtrProviderContext): void => { await riskEngineRoutes.init(); }); - it('calculates and persists risk scores for both types of entities', async () => { + it('@skipInQA calculates and persists risk scores for both types of entities', async () => { await waitForRiskScoresToBePresent({ es, log, scoreCount: 20 }); const riskScores = await readRiskScores(es); @@ -235,74 +227,6 @@ export default ({ getService }: FtrProviderContext): void => { expect(scoredIdentifiers.includes('user.name')).to.be(true); }); }); - - describe('with alerts in a non-default space', () => { - let namespace: string; - let index: string[]; - let documentId: string; - let createAndSyncRuleAndAlertsForOtherSpace: ReturnType< - typeof createAndSyncRuleAndAlertsFactory - >; - - beforeEach(async () => { - documentId = uuidv4(); - namespace = uuidv4(); - index = [`risk-score.risk-score-${namespace}`]; - - createAndSyncRuleAndAlertsForOtherSpace = createAndSyncRuleAndAlertsFactory({ - supertest, - log, - namespace, - }); - const riskEngineRoutesForNamespace = riskEngineRouteHelpersFactory(supertest, namespace); - - const spaces = getService('spaces'); - await spaces.create({ - id: namespace, - name: namespace, - disabledFeatures: [], - }); - - const baseEvent = buildDocument({ host: { name: 'host-1' } }, documentId); - await indexListOfDocuments( - Array(10) - .fill(baseEvent) - .map((_baseEvent, _index) => ({ - ..._baseEvent, - 'host.name': `host-${_index}`, - })) - ); - - await createAndSyncRuleAndAlertsForOtherSpace({ - query: `id: ${documentId}`, - alerts: 10, - riskScore: 40, - }); - - await riskEngineRoutesForNamespace.init(); - }); - - afterEach(async () => { - await getService('spaces').delete(namespace); - await deleteRiskScoreIndices({ log, es, namespace }); - }); - - it('calculates and persists risk scores for alert documents', async () => { - await waitForRiskScoresToBePresent({ - es, - log, - scoreCount: 10, - index, - }); - - const scores = await readRiskScores(es, index); - expect(normalizeScores(scores).map(({ id_value: idValue }) => idValue)).to.eql( - Array(10) - .fill(0) - .map((_, _index) => `host-${_index}`) - ); - }); - }); }); }); }; diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/risk_scoring_task/task_execution_nondefault_spaces.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/risk_scoring_task/task_execution_nondefault_spaces.ts new file mode 100644 index 00000000000000..cf9bf98f88d9d4 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/risk_scoring_task/task_execution_nondefault_spaces.ts @@ -0,0 +1,133 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { v4 as uuidv4 } from 'uuid'; +import { + deleteAllAlerts, + deleteAllRules, + dataGeneratorFactory, +} from '../../../../detections_response/utils'; +import { + buildDocument, + createAndSyncRuleAndAlertsFactory, + readRiskScores, + waitForRiskScoresToBePresent, + normalizeScores, + riskEngineRouteHelpersFactory, + cleanRiskEngine, + deleteRiskScoreIndices, +} from '../../../utils'; + +import { FtrProviderContextWithSpaces } from './ftr_provider_context_with_spaces'; + +export default ({ getService }: FtrProviderContextWithSpaces): void => { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + const es = getService('es'); + const log = getService('log'); + const kibanaServer = getService('kibanaServer'); + + describe('@ess Risk Scoring Task in non-default space', () => { + context('with auditbeat data', () => { + const { indexListOfDocuments } = dataGeneratorFactory({ + es, + index: 'ecs_compliant', + log, + }); + + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/security_solution/ecs_compliant'); + }); + + after(async () => { + await esArchiver.unload( + 'x-pack/test/functional/es_archives/security_solution/ecs_compliant' + ); + }); + + beforeEach(async () => { + await cleanRiskEngine({ kibanaServer, es, log }); + await deleteAllAlerts(supertest, log, es); + await deleteAllRules(supertest, log); + }); + + afterEach(async () => { + await cleanRiskEngine({ kibanaServer, es, log }); + await deleteAllAlerts(supertest, log, es); + await deleteAllRules(supertest, log); + }); + describe('with alerts in a non-default space', () => { + let namespace: string; + let index: string[]; + let documentId: string; + let createAndSyncRuleAndAlertsForOtherSpace: ReturnType< + typeof createAndSyncRuleAndAlertsFactory + >; + + beforeEach(async () => { + documentId = uuidv4(); + namespace = uuidv4(); + index = [`risk-score.risk-score-${namespace}`]; + + createAndSyncRuleAndAlertsForOtherSpace = createAndSyncRuleAndAlertsFactory({ + supertest, + log, + namespace, + }); + const riskEngineRoutesForNamespace = riskEngineRouteHelpersFactory(supertest, namespace); + + const spaces = getService('spaces'); + await spaces.create({ + id: namespace, + name: namespace, + disabledFeatures: [], + }); + + const baseEvent = buildDocument({ host: { name: 'host-1' } }, documentId); + await indexListOfDocuments( + Array(10) + .fill(baseEvent) + .map((_baseEvent, _index) => ({ + ..._baseEvent, + 'host.name': `host-${_index}`, + })) + ); + + await createAndSyncRuleAndAlertsForOtherSpace({ + query: `id: ${documentId}`, + alerts: 10, + riskScore: 40, + }); + + await riskEngineRoutesForNamespace.init(); + }); + + afterEach(async () => { + await getService('spaces').delete(namespace); + await deleteRiskScoreIndices({ log, es, namespace }); + }); + + it('calculates and persists risk scores for alert documents', async () => { + await waitForRiskScoresToBePresent({ + es, + log, + scoreCount: 10, + index, + }); + + const scores = await readRiskScores(es, index); + expect(normalizeScores(scores).map(({ id_value: idValue }) => idValue)).to.eql( + Array(10) + .fill(0) + .map((_, _index) => `host-${_index}`) + ); + }); + }); + }); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/risk_engine/telemetry_usage.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/telemetry_usage.ts similarity index 78% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/group10/risk_engine/telemetry_usage.ts rename to x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/telemetry_usage.ts index 2e7888fe00591d..f68a8c1dd3e60d 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/risk_engine/telemetry_usage.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/telemetry_usage.ts @@ -7,21 +7,21 @@ import expect from '@kbn/expect'; import { v4 as uuidv4 } from 'uuid'; -import type { FtrProviderContext } from '../../../../common/ftr_provider_context'; -import { deleteAllRules, deleteAllAlerts, getRiskEngineStats } from '../../../utils'; +import { + deleteAllRules, + deleteAllAlerts, + dataGeneratorFactory, +} from '../../../detections_response/utils'; import { buildDocument, createAndSyncRuleAndAlertsFactory, - deleteRiskEngineTask, - deleteRiskScoreIndices, waitForRiskScoresToBePresent, riskEngineRouteHelpersFactory, - cleanRiskEngineConfig, - clearTransforms, -} from './utils'; -import { dataGeneratorFactory } from '../../../utils/data_generator'; + cleanRiskEngine, + getRiskEngineStats, +} from '../../utils'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; -// eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); @@ -32,7 +32,7 @@ export default ({ getService }: FtrProviderContext) => { const createAndSyncRuleAndAlerts = createAndSyncRuleAndAlertsFactory({ supertest, log }); const riskEngineRoutes = riskEngineRouteHelpersFactory(supertest); - describe('Risk engine telemetry', async () => { + describe('@ess @serverless telemetry', async () => { const { indexListOfDocuments } = dataGeneratorFactory({ es, index: 'ecs_compliant', @@ -49,12 +49,9 @@ export default ({ getService }: FtrProviderContext) => { }); beforeEach(async () => { - await cleanRiskEngineConfig({ kibanaServer }); - await deleteRiskEngineTask({ es, log }); - await deleteRiskScoreIndices({ log, es }); + await cleanRiskEngine({ kibanaServer, es, log }); await deleteAllAlerts(supertest, log, es); await deleteAllRules(supertest, log); - await clearTransforms({ es, log }); }); describe('Risk engine not enabled', () => { @@ -67,7 +64,6 @@ export default ({ getService }: FtrProviderContext) => { }); }); - // FLAKY: https://github.com/elastic/kibana/issues/168429 describe('Risk engine enabled', () => { let hostId: string; let userId: string; @@ -105,12 +101,9 @@ export default ({ getService }: FtrProviderContext) => { }); afterEach(async () => { - await cleanRiskEngineConfig({ kibanaServer }); - await deleteRiskEngineTask({ es, log }); - await deleteRiskScoreIndices({ log, es }); + await cleanRiskEngine({ kibanaServer, es, log }); await deleteAllAlerts(supertest, log, es); await deleteAllRules(supertest, log); - await clearTransforms({ es, log }); }); it('should return riskEngineMetrics with expected values', async () => { @@ -132,8 +125,6 @@ export default ({ getService }: FtrProviderContext) => { all_host_risk_scores_total_day: 10, }; expect(otherStats).to.eql(expected); - expect(allRiskScoreIndexSize).to.be.greaterThan(0); - expect(uniqueRiskScoreIndexSize).to.be.greaterThan(0); }); }); }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/get_risk_engine_stats.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/get_risk_engine_stats.ts new file mode 100644 index 00000000000000..c5343372ce99a4 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/get_risk_engine_stats.ts @@ -0,0 +1,73 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ToolingLog } from '@kbn/tooling-log'; +import type SuperTest from 'supertest'; +import type { DetectionMetrics } from '@kbn/security-solution-plugin/server/usage/detections/types'; +import type { RiskEngineMetrics } from '@kbn/security-solution-plugin/server/usage/risk_engine/types'; +import { + ELASTIC_HTTP_VERSION_HEADER, + X_ELASTIC_INTERNAL_ORIGIN_REQUEST, +} from '@kbn/core-http-common'; + +import { getStatsUrl } from '../../../../detection_engine_api_integration/utils/get_stats_url'; +import { + getDetectionMetricsFromBody, + getRiskEngineMetricsFromBody, +} from '../../../../detection_engine_api_integration/utils/get_detection_metrics_from_body'; + +/** + * Gets the stats from the stats endpoint. + * @param supertest The supertest agent. + * @returns The detection metrics + */ +export const getStats = async ( + supertest: SuperTest.SuperTest, + log: ToolingLog +): Promise => { + const response = await supertest + .post(getStatsUrl()) + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send({ unencrypted: true, refreshCache: true }); + if (response.status !== 200) { + log.error( + `Did not get an expected 200 "ok" when getting the stats for detections. CI issues could happen. Suspect this line if you are seeing CI issues. body: ${JSON.stringify( + response.body + )}, status: ${JSON.stringify(response.status)}` + ); + } + + return getDetectionMetricsFromBody(response.body); +}; + +/** + * Gets the stats from the stats endpoint. + * @param supertest The supertest agent. + * @returns The detection metrics + */ +export const getRiskEngineStats = async ( + supertest: SuperTest.SuperTest, + log: ToolingLog +): Promise => { + const response = await supertest + .post(getStatsUrl()) + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send({ unencrypted: true, refreshCache: true }); + if (response.status !== 200) { + log.error( + `Did not get an expected 200 "ok" when getting the stats for risk engine. CI issues could happen. Suspect this line if you are seeing CI issues. body: ${JSON.stringify( + response.body + )}, status: ${JSON.stringify(response.status)}` + ); + } + + return getRiskEngineMetricsFromBody(response.body); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/index.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/index.ts new file mode 100644 index 00000000000000..00eb2adafd2e8f --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +export * from './risk_engine'; +export * from './get_risk_engine_stats'; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/risk_engine/utils.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/risk_engine.ts similarity index 87% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/group10/risk_engine/utils.ts rename to x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/risk_engine.ts index 7af59dfab7cf85..48c7763d7a9d64 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/risk_engine/utils.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/risk_engine.ts @@ -5,13 +5,16 @@ * 2.0. */ -import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common'; +import { + ELASTIC_HTTP_VERSION_HEADER, + X_ELASTIC_INTERNAL_ORIGIN_REQUEST, +} from '@kbn/core-http-common'; import { v4 as uuidv4 } from 'uuid'; import SuperTest from 'supertest'; import type { Client } from '@elastic/elasticsearch'; import type { ToolingLog } from '@kbn/tooling-log'; import type { EcsRiskScore, RiskScore } from '@kbn/security-solution-plugin/common/risk_engine'; -import { riskEngineConfigurationTypeName } from '@kbn/security-solution-plugin/server/lib/risk_engine/saved_object'; +import { riskEngineConfigurationTypeName } from '@kbn/security-solution-plugin/server/lib/entity_analytics/risk_engine/saved_object'; import type { KbnClient } from '@kbn/test'; import { RISK_ENGINE_INIT_URL, @@ -21,13 +24,13 @@ import { } from '@kbn/security-solution-plugin/common/constants'; import { createRule, - waitForSignalsToBePresent, + waitForAlertsToBePresent, waitForRuleSuccess, - getRuleForSignalTesting, + getRuleForAlertTesting, countDownTest, waitFor, routeWithNamespace, -} from '../../../utils'; +} from '../../detections_response/utils'; const sanitizeScore = (score: Partial): Partial => { delete score['@timestamp']; @@ -79,7 +82,7 @@ export const createAndSyncRuleAndAlertsFactory = query: string; riskScoreOverride?: string; }): Promise => { - const rule = getRuleForSignalTesting(['ecs_compliant']); + const rule = getRuleForAlertTesting(['ecs_compliant']); const { id } = await createRule( supertest, log, @@ -99,7 +102,7 @@ export const createAndSyncRuleAndAlertsFactory = namespace ); await waitForRuleSuccess({ supertest, log, id, namespace }); - await waitForSignalsToBePresent(supertest, log, alerts, [id], namespace); + await waitForAlertsToBePresent(supertest, log, alerts, [id], namespace); }; export const deleteRiskScoreIndices = async ({ @@ -119,7 +122,7 @@ export const deleteRiskScoreIndices = async ({ }), ]); } catch (e) { - log.error(`Error deleting risk score indices: ${e.message}`); + log.warning(`Error deleting risk score indices: ${e.message}`); } }; @@ -302,6 +305,24 @@ export const cleanRiskEngineConfig = async ({ } }; +/** + * General helper for cleaning up risk engine artifacts. This should be used before and after any risk engine tests so as not to pollute the test environment. + */ +export const cleanRiskEngine = async ({ + es, + kibanaServer, + log, +}: { + es: Client; + kibanaServer: KbnClient; + log: ToolingLog; +}): Promise => { + await deleteRiskEngineTask({ es, log }); + await cleanRiskEngineConfig({ kibanaServer }); + await clearTransforms({ es, log }); + await deleteRiskScoreIndices({ log, es }); +}; + export const updateRiskEngineConfigSO = async ({ attributes, kibanaServer, @@ -344,7 +365,7 @@ export const clearTransforms = async ({ force: true, }); } catch (e) { - log.error(`Error deleting risk_score_latest_transform_default: ${e.message}`); + log.warning(`Error deleting risk_score_latest_transform_default: ${e.message}`); } }; @@ -364,7 +385,7 @@ export const clearLegacyTransforms = async ({ try { await Promise.all(transforms); } catch (e) { - log.error(`Error deleting legacy transforms: ${e.message}`); + log.warning(`Error deleting legacy transforms: ${e.message}`); } }; @@ -381,6 +402,7 @@ export const clearLegacyDashboards = async ({ '/internal/risk_score/prebuilt_content/saved_objects/_bulk_delete/hostRiskScoreDashboards' ) .set('kbn-xsrf', 'true') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .send() .expect(200); @@ -389,10 +411,11 @@ export const clearLegacyDashboards = async ({ '/internal/risk_score/prebuilt_content/saved_objects/_bulk_delete/userRiskScoreDashboards' ) .set('kbn-xsrf', 'true') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .send() .expect(200); } catch (e) { - log.error(`Error deleting legacy dashboards: ${e.message}`); + log.warning(`Error deleting legacy dashboards: ${e.message}`); } }; @@ -450,6 +473,7 @@ export const riskEngineRouteHelpersFactory = ( .post(routeWithNamespace(RISK_ENGINE_INIT_URL, namespace)) .set('kbn-xsrf', 'true') .set('elastic-api-version', '1') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .send() .expect(200), @@ -466,6 +490,7 @@ export const riskEngineRouteHelpersFactory = ( .post(routeWithNamespace(RISK_ENGINE_ENABLE_URL, namespace)) .set('kbn-xsrf', 'true') .set('elastic-api-version', '1') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .send() .expect(200), @@ -474,6 +499,7 @@ export const riskEngineRouteHelpersFactory = ( .post(routeWithNamespace(RISK_ENGINE_DISABLE_URL, namespace)) .set('kbn-xsrf', 'true') .set('elastic-api-version', '1') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .send() .expect(200), }); @@ -487,6 +513,7 @@ export const installLegacyRiskScore = async ({ .post('/internal/risk_score') .set('kbn-xsrf', 'true') .set(ELASTIC_HTTP_VERSION_HEADER, '1') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .send({ riskScoreEntity: 'host' }) .expect(200); @@ -494,6 +521,7 @@ export const installLegacyRiskScore = async ({ .post('/internal/risk_score') .set('kbn-xsrf', 'true') .set(ELASTIC_HTTP_VERSION_HEADER, '1') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .send({ riskScoreEntity: 'user' }) .expect(200); @@ -503,6 +531,7 @@ export const installLegacyRiskScore = async ({ ) .set('kbn-xsrf', 'true') .set(ELASTIC_HTTP_VERSION_HEADER, '1') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .send() .expect(200); @@ -512,6 +541,7 @@ export const installLegacyRiskScore = async ({ ) .set('kbn-xsrf', 'true') .set(ELASTIC_HTTP_VERSION_HEADER, '1') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .send() .expect(200); }; diff --git a/x-pack/test/security_solution_api_integration/tsconfig.json b/x-pack/test/security_solution_api_integration/tsconfig.json index b2e2715a52ccd1..4dfd3ef6ba30d9 100644 --- a/x-pack/test/security_solution_api_integration/tsconfig.json +++ b/x-pack/test/security_solution_api_integration/tsconfig.json @@ -30,6 +30,9 @@ "@kbn/core-saved-objects-server", "@kbn/core", "@kbn/alerting-plugin", - "@kbn/securitysolution-ecs" + "@kbn/core-http-common", + "@kbn/securitysolution-ecs", + "@kbn/fleet-plugin", + "@kbn/repo-info", ] } diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/alert_status.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/alert_status.cy.ts index c55c8f62dc4a9c..2ec99abb00db48 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/alert_status.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/alert_status.cy.ts @@ -37,10 +37,13 @@ import { visit } from '../../../tasks/navigation'; import { ALERTS_URL } from '../../../urls/navigation'; -// FLAKY: https://github.com/elastic/kibana/issues/169091 -describe('Changing alert status', () => { +describe('Changing alert status', { tags: ['@ess', '@serverless'] }, () => { before(() => { - cy.task('esArchiverLoad', { archiveName: 'auditbeat_big' }); + cy.task('esArchiverLoad', { archiveName: 'auditbeat_multiple' }); + }); + + after(() => { + cy.task('esArchiverUnload', 'auditbeat_multiple'); }); context('Opening alerts', { tags: ['@ess', '@serverless'] }, () => { @@ -56,10 +59,6 @@ describe('Changing alert status', () => { waitForAlerts(); }); - after(() => { - cy.task('esArchiverUnload', 'auditbeat_big'); - }); - it('can mark a closed alert as open', () => { waitForAlertsToPopulate(); cy.get(ALERTS_COUNT) diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/alert_tags.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/alert_tags.cy.ts index ee3955576a2726..162c63ad3ce48a 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/alert_tags.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/alert_tags.cy.ts @@ -26,19 +26,24 @@ import { } from '../../../screens/alerts'; describe('Alert tagging', { tags: ['@ess', '@serverless'] }, () => { + before(() => { + cy.task('esArchiverLoad', { archiveName: 'endpoint' }); + cy.task('esArchiverLoad', { archiveName: 'auditbeat_multiple' }); + }); + + after(() => { + cy.task('esArchiverUnload', 'endpoint'); + cy.task('esArchiverUnload', 'auditbeat_multiple'); + }); + beforeEach(() => { login(); deleteAlertsAndRules(); - cy.task('esArchiverLoad', { archiveName: 'endpoint' }); createRule(getNewRule({ rule_id: 'new custom rule' })); visitWithTimeRange(ALERTS_URL); waitForAlertsToPopulate(); }); - afterEach(() => { - cy.task('esArchiverUnload', 'endpoint'); - }); - it('Add and remove a tag using the alert bulk action menu', () => { // Add a tag to one alert selectNumberOfAlerts(1); @@ -66,13 +71,13 @@ describe('Alert tagging', { tags: ['@ess', '@serverless'] }, () => { updateAlertTags(); cy.get(ALERTS_TABLE_ROW_LOADER).should('not.exist'); // Then add tags to both alerts - selectNumberOfAlerts(2); + selectNumberOfAlerts(5); openAlertTaggingBulkActionMenu(); cy.get(MIXED_ALERT_TAG).contains('Duplicate'); clickAlertTag('Duplicate'); updateAlertTags(); cy.get(ALERTS_TABLE_ROW_LOADER).should('not.exist'); - selectNumberOfAlerts(2); + selectNumberOfAlerts(5); openAlertTaggingBulkActionMenu(); cy.get(SELECTED_ALERT_TAG).contains('Duplicate'); }); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/event_correlation_rule.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/event_correlation_rule.cy.ts index eeca0d06bcc572..b8954606618587 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/event_correlation_rule.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/event_correlation_rule.cy.ts @@ -71,7 +71,7 @@ describe('EQL rules', { tags: ['@ess', '@serverless'] }, () => { const mitreAttack = rule.threat; const expectedMitre = formatMitreAttackDescription(mitreAttack ?? []); const expectedNumberOfRules = 1; - const expectedNumberOfAlerts = '2 alerts'; + const expectedNumberOfAlerts = '1 alert'; it('Creates and enables a new EQL rule', function () { visit(CREATE_RULE_URL); @@ -143,11 +143,12 @@ describe('EQL rules', { tags: ['@ess', '@serverless'] }, () => { const rule = getEqlSequenceRule(); - beforeEach(() => { - cy.task('esArchiverLoad', { archiveName: 'auditbeat_big' }); + before(() => { + cy.task('esArchiverLoad', { archiveName: 'auditbeat_multiple' }); }); - afterEach(() => { - cy.task('esArchiverUnload', 'auditbeat_big'); + + after(() => { + cy.task('esArchiverUnload', 'auditbeat_multiple'); }); it('Creates and enables a new EQL rule with a sequence', function () { diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/override.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/override.cy.ts index 7b8e5b0744c72e..585cd9187f3e00 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/override.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/override.cy.ts @@ -146,8 +146,8 @@ describe('Rules override', { tags: ['@ess', '@serverless'] }, () => { cy.get(ALERTS_COUNT) .invoke('text') .should('match', /^[1-9].+$/); // Any number of alerts - cy.get(ALERT_GRID_CELL).contains('auditbeat'); - cy.get(ALERT_GRID_CELL).contains('critical'); + cy.get(ALERT_GRID_CELL).contains('winlogbeat'); + cy.get(ALERT_GRID_CELL).contains('high'); cy.get(ALERT_GRID_CELL).contains('80'); }); }); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_edit_rules_actions.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_edit_rules_actions.cy.ts index d543843641b788..ac6c723dbc0c65 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_edit_rules_actions.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_edit_rules_actions.cy.ts @@ -76,7 +76,7 @@ const expectedSlackMessage = 'Slack action test message'; describe( 'Detection rules, bulk edit of rule actions', - { tags: ['@ess', '@serverless', '@brokenInServerlessQA'] }, + { tags: ['@ess', '@serverless', '@brokenInServerless', '@brokenInServerlessQA'] }, () => { beforeEach(() => { login(); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/entity_analytics_management_page.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/entity_analytics_management_page.cy.ts index a676fe5038d3e0..24d13322f7d9da 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/entity_analytics_management_page.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/entity_analytics_management_page.cy.ts @@ -38,7 +38,6 @@ import { previewErrorButtonClick, } from '../../tasks/entity_analytics'; -// TODO: https://github.com/elastic/kibana/issues/161539 describe( 'Entity analytics management page', { diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/add_edit_endpoint_exception.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/add_edit_endpoint_exception.cy.ts index 7474fd2e5cf2d1..8ec40a0e364360 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/add_edit_endpoint_exception.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/add_edit_endpoint_exception.cy.ts @@ -50,179 +50,182 @@ import { createEndpointExceptionListItem, } from '../../../tasks/api_calls/exceptions'; -// TODO: https://github.com/elastic/kibana/issues/161539 -describe( - 'Add endpoint exception from rule details', - { tags: ['@ess', '@serverless', '@brokenInServerless'] }, - () => { - const ITEM_NAME = 'Sample Exception List Item'; - const NEW_ITEM_NAME = 'Exception item-EDITED'; - const ITEM_FIELD = 'event.code'; - const FIELD_DIFFERENT_FROM_EXISTING_ITEM_FIELD = 'agent.type'; - +describe('Add endpoint exception from rule details', { tags: ['@ess', '@serverless'] }, () => { + const ITEM_NAME = 'Sample Exception List Item'; + const NEW_ITEM_NAME = 'Exception item-EDITED'; + const ITEM_FIELD = 'event.code'; + const FIELD_DIFFERENT_FROM_EXISTING_ITEM_FIELD = 'agent.type'; + + before(() => { + cy.task('esArchiverLoad', { archiveName: 'auditbeat_multiple' }); + }); + + after(() => { + cy.task('esArchiverUnload', 'auditbeat_multiple'); + }); + + beforeEach(() => { + deleteExceptionLists(); + deleteEndpointExceptionList(); + login(); + deleteAlertsAndRules(); + }); + + describe('without exception items', () => { beforeEach(() => { - deleteExceptionLists(); - deleteEndpointExceptionList(); - login(); - deleteAlertsAndRules(); - }); - - describe('without exception items', () => { - beforeEach(() => { - createEndpointExceptionList().then((response) => { - createRule( - getNewRule({ - query: 'event.code:*', - index: ['auditbeat*'], - exceptions_list: [ - { - id: response.body.id, - list_id: response.body.list_id, - type: response.body.type, - namespace_type: response.body.namespace_type, - }, - ], - rule_id: '2', - enabled: false, - }) - ).then((rule) => visitRuleDetailsPage(rule.body.id, { tab: 'endpoint_exceptions' })); - }); + createEndpointExceptionList().then((response) => { + createRule( + getNewRule({ + query: 'event.code:*', + index: ['auditbeat*'], + exceptions_list: [ + { + id: response.body.id, + list_id: response.body.list_id, + type: response.body.type, + namespace_type: response.body.namespace_type, + }, + ], + rule_id: '2', + enabled: false, + }) + ).then((rule) => visitRuleDetailsPage(rule.body.id, { tab: 'endpoint_exceptions' })); }); + }); - it('creates an exception item', () => { - // when no exceptions exist, empty component shows with action to add exception - cy.get(NO_EXCEPTIONS_EXIST_PROMPT).should('exist'); + it('creates an exception item', () => { + // when no exceptions exist, empty component shows with action to add exception + cy.get(NO_EXCEPTIONS_EXIST_PROMPT).should('exist'); - // open add exception modal - openExceptionFlyoutFromEmptyViewerPrompt(); + // open add exception modal + openExceptionFlyoutFromEmptyViewerPrompt(); - // submit button is disabled if no paramerters were added - cy.get(CONFIRM_BTN).should('have.attr', 'disabled'); + // submit button is disabled if no paramerters were added + cy.get(CONFIRM_BTN).should('have.attr', 'disabled'); - // for endpoint exceptions, must specify OS - selectOs('windows'); + // for endpoint exceptions, must specify OS + selectOs('windows'); - // add exception item conditions - addExceptionConditions({ - field: 'event.code', - operator: 'is', - values: ['foo'], - }); + // add exception item conditions + addExceptionConditions({ + field: 'event.code', + operator: 'is', + values: ['foo'], + }); - // Name is required so want to check that submit is still disabled - cy.get(CONFIRM_BTN).should('have.attr', 'disabled'); + // Name is required so want to check that submit is still disabled + cy.get(CONFIRM_BTN).should('have.attr', 'disabled'); - // add exception item name - addExceptionFlyoutItemName(ITEM_NAME); + // add exception item name + addExceptionFlyoutItemName(ITEM_NAME); - // Option to add to rule or add to list should NOT appear - cy.get(ADD_TO_RULE_OR_LIST_SECTION).should('not.exist'); + // Option to add to rule or add to list should NOT appear + cy.get(ADD_TO_RULE_OR_LIST_SECTION).should('not.exist'); - // not testing close alert functionality here, just ensuring that the options appear as expected - cy.get(CLOSE_SINGLE_ALERT_CHECKBOX).should('not.exist'); - cy.get(CLOSE_ALERTS_CHECKBOX).should('exist'); + // not testing close alert functionality here, just ensuring that the options appear as expected + cy.get(CLOSE_SINGLE_ALERT_CHECKBOX).should('not.exist'); + cy.get(CLOSE_ALERTS_CHECKBOX).should('exist'); - // submit - submitNewExceptionItem(); + // submit + submitNewExceptionItem(); - // new exception item displays - cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1); - }); + // new exception item displays + cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1); }); + }); + + describe('with exception items', () => { + beforeEach(() => { + createEndpointExceptionList().then((response) => { + createEndpointExceptionListItem({ + comments: [], + description: 'Exception list item', + entries: [ + { + field: ITEM_FIELD, + operator: 'included', + type: 'match', + value: 'foo', + }, + ], + name: ITEM_NAME, + tags: [], + type: 'simple', + os_types: ['windows'], + }); - describe('with exception items', () => { - beforeEach(() => { - createEndpointExceptionList().then((response) => { - createEndpointExceptionListItem({ - comments: [], - description: 'Exception list item', - entries: [ + createRule( + getNewRule({ + name: 'Rule with exceptions', + query: 'event.code:*', + index: ['auditbeat*'], + exceptions_list: [ { - field: ITEM_FIELD, - operator: 'included', - type: 'match', - value: 'foo', + id: response.body.id, + list_id: response.body.list_id, + type: response.body.type, + namespace_type: response.body.namespace_type, }, ], - name: ITEM_NAME, - tags: [], - type: 'simple', - os_types: ['windows'], - }); - - createRule( - getNewRule({ - name: 'Rule with exceptions', - query: 'event.code:*', - index: ['auditbeat*'], - exceptions_list: [ - { - id: response.body.id, - list_id: response.body.list_id, - type: response.body.type, - namespace_type: response.body.namespace_type, - }, - ], - rule_id: '2', - enabled: false, - }) - ).then((rule) => { - visitRuleDetailsPage(rule.body.id, { tab: 'endpoint_exceptions' }); - waitForRuleDetailsPageToBeLoaded('Rule with exceptions'); - }); + rule_id: '2', + enabled: false, + }) + ).then((rule) => { + visitRuleDetailsPage(rule.body.id, { tab: 'endpoint_exceptions' }); + waitForRuleDetailsPageToBeLoaded('Rule with exceptions'); }); }); + }); - it('edits an endpoint exception item', () => { - // displays existing exception items - cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1); - cy.get(NO_EXCEPTIONS_EXIST_PROMPT).should('not.exist'); - cy.get(EXCEPTION_CARD_ITEM_NAME).should('have.text', ITEM_NAME); - cy.get(EXCEPTION_CARD_ITEM_CONDITIONS).should('have.text', ` ${ITEM_FIELD}IS foo`); + it('edits an endpoint exception item', () => { + // displays existing exception items + cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1); + cy.get(NO_EXCEPTIONS_EXIST_PROMPT).should('not.exist'); + cy.get(EXCEPTION_CARD_ITEM_NAME).should('have.text', ITEM_NAME); + cy.get(EXCEPTION_CARD_ITEM_CONDITIONS).should('have.text', ` ${ITEM_FIELD}IS foo`); - // open edit exception modal - openEditException(); + // open edit exception modal + openEditException(); - // edit exception item name - editExceptionFlyoutItemName(NEW_ITEM_NAME); + // edit exception item name + editExceptionFlyoutItemName(NEW_ITEM_NAME); - // check that the existing item's field is being populated - cy.get(EXCEPTION_ITEM_CONTAINER) - .eq(0) - .find(FIELD_INPUT_PARENT) - .eq(0) - .should('have.text', ITEM_FIELD); - cy.get(VALUES_INPUT).should('have.text', 'foo'); + // check that the existing item's field is being populated + cy.get(EXCEPTION_ITEM_CONTAINER) + .eq(0) + .find(FIELD_INPUT_PARENT) + .eq(0) + .should('have.text', ITEM_FIELD); + cy.get(VALUES_INPUT).should('have.text', 'foo'); - // edit conditions - editException(FIELD_DIFFERENT_FROM_EXISTING_ITEM_FIELD, 0, 0); + // edit conditions + editException(FIELD_DIFFERENT_FROM_EXISTING_ITEM_FIELD, 0, 0); - // submit - submitEditedExceptionItem(); + // submit + submitEditedExceptionItem(); - // new exception item displays - cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1); + // new exception item displays + cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1); - // check that updates stuck - cy.get(EXCEPTION_CARD_ITEM_NAME).should('have.text', NEW_ITEM_NAME); - cy.get(EXCEPTION_CARD_ITEM_CONDITIONS).should('have.text', ' agent.typeIS foo'); - }); + // check that updates stuck + cy.get(EXCEPTION_CARD_ITEM_NAME).should('have.text', NEW_ITEM_NAME); + cy.get(EXCEPTION_CARD_ITEM_CONDITIONS).should('have.text', ' agent.typeIS foo'); + }); - it('allows user to search for items', () => { - cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1); + it('allows user to search for items', () => { + cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1); - // can search for an exception value - searchForExceptionItem('foo'); + // can search for an exception value + searchForExceptionItem('foo'); - // new exception item displays - cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1); + // new exception item displays + cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1); - // displays empty search result view if no matches found - searchForExceptionItem('abc'); + // displays empty search result view if no matches found + searchForExceptionItem('abc'); - // new exception item displays - cy.get(NO_EXCEPTIONS_SEARCH_RESULTS_PROMPT).should('exist'); - }); + // new exception item displays + cy.get(NO_EXCEPTIONS_SEARCH_RESULTS_PROMPT).should('exist'); }); - } -); + }); +}); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/manage_exceptions.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/manage_exceptions.cy.ts index eba0c3a64570fd..023140d420f2a7 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/manage_exceptions.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/manage_exceptions.cy.ts @@ -6,6 +6,7 @@ */ import type { RuleResponse } from '@kbn/security-solution-plugin/common/api/detection_engine'; +import { MAX_COMMENT_LENGTH } from '@kbn/security-solution-plugin/common/constants'; import { getNewRule } from '../../../objects/rule'; import { login } from '../../../tasks/login'; import { visit } from '../../../tasks/navigation'; @@ -20,9 +21,7 @@ import { submitEditedExceptionItem, submitNewExceptionItem, deleteFirstExceptionItemInListDetailPage, - dismissExceptionItemErrorCallOut, addExceptionHugeComment, - submitNewExceptionItemWithFailure, editExceptionComment, } from '../../../tasks/exceptions'; import { EXCEPTIONS_URL } from '../../../urls/navigation'; @@ -42,7 +41,6 @@ import { } from '../../../tasks/exceptions_table'; import { visitRuleDetailsPage } from '../../../tasks/rule_details'; import { deleteEndpointExceptionList, deleteExceptionLists } from '../../../tasks/common'; -import { closeErrorToast } from '../../../tasks/alerts_detection_rules'; describe('Add, edit and delete exception', { tags: ['@ess', '@serverless'] }, () => { beforeEach(() => { @@ -147,7 +145,7 @@ describe('Add, edit and delete exception', { tags: ['@ess', '@serverless'] }, () cy.get(EMPTY_EXCEPTIONS_VIEWER).should('exist'); }); - it('should handle huge text as a comment gracefully and allow user create exception item after user updates the comment', function () { + it('should not allow to add huge text as a comment', function () { createSharedExceptionList( { name: 'Newly created list', description: 'This is my list.' }, true @@ -173,26 +171,19 @@ describe('Add, edit and delete exception', { tags: ['@ess', '@serverless'] }, () linkFirstSharedListOnExceptionFlyout(); // add exception comment which is super long - addExceptionHugeComment([...new Array(5000).keys()].map((_) => `Test text!`).join('')); - - // submit - submitNewExceptionItemWithFailure(); + addExceptionHugeComment( + [...new Array(MAX_COMMENT_LENGTH + 1).keys()].map((_) => 'a').join('') + ); - // Failed to add exception due to comment length and submit button should be disabled + // submit button should be disabled due to comment length cy.get(CONFIRM_BTN).should('have.attr', 'disabled'); - // Close error toast - closeErrorToast(); - - // Dismiss error callout - dismissExceptionItemErrorCallOut(); - - // Submit button should be enabled after we dismissed error callout - cy.get(CONFIRM_BTN).should('not.have.attr', 'disabled'); - // update exception comment to a reasonable (length wise) text editExceptionComment('Exceptional comment'); + // submit button should be enabled + cy.get(CONFIRM_BTN).should('not.have.attr', 'disabled'); + // submit submitNewExceptionItem(); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/explore/dashboards/entity_analytics.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/explore/dashboards/entity_analytics.cy.ts index 83b9d086db5f20..7bb492d51d25c9 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/explore/dashboards/entity_analytics.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/explore/dashboards/entity_analytics.cy.ts @@ -57,9 +57,9 @@ import { import { deleteRiskEngineConfiguration } from '../../../tasks/api_calls/risk_engine'; import { enableRiskEngine } from '../../../tasks/entity_analytics'; -const TEST_USER_ALERTS = 2; +const TEST_USER_ALERTS = 1; const TEST_USER_NAME = 'test'; -const SIEM_KIBANA_HOST_ALERTS = 2; +const SIEM_KIBANA_HOST_ALERTS = 1; const SIEM_KIBANA_HOST_NAME = 'siem-kibana'; const DATE_FORMAT = 'MMM D, YYYY @ HH:mm:ss.SSS'; const DATE_BEFORE_ALERT_CREATION = moment().format(DATE_FORMAT); @@ -67,10 +67,15 @@ const OLDEST_DATE = moment('2019-01-19T16:22:56.217Z').format(DATE_FORMAT); describe('Entity Analytics Dashboard', { tags: ['@ess', '@serverless'] }, () => { before(() => { + cy.task('esArchiverLoad', { archiveName: 'auditbeat_multiple' }); login(); deleteRiskEngineConfiguration(); }); + after(() => { + cy.task('esArchiverUnload', 'auditbeat_multiple'); + }); + describe('legacy risk score', () => { describe('Without data', () => { beforeEach(() => { diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/explore/hosts/events_viewer.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/explore/hosts/events_viewer.cy.ts index 20884e070cf9ae..0a211a58feba30 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/explore/hosts/events_viewer.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/explore/hosts/events_viewer.cy.ts @@ -48,11 +48,11 @@ const defaultHeadersInDefaultEcsCategory = [ describe('Events Viewer', { tags: ['@ess', '@serverless'] }, () => { before(() => { - cy.task('esArchiverLoad', { archiveName: 'auditbeat_big' }); + cy.task('esArchiverLoad', { archiveName: 'auditbeat_multiple' }); }); after(() => { - cy.task('esArchiverUnload', 'auditbeat_big'); + cy.task('esArchiverUnload', 'auditbeat_multiple'); }); context('Fields rendering', () => { diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/explore/overview/overview.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/explore/overview/overview.cy.ts index 0fcb7f86cd0de7..1205d2420b7ab8 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/explore/overview/overview.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/explore/overview/overview.cy.ts @@ -64,13 +64,6 @@ describe('Overview Page', { tags: ['@ess', '@serverless', '@serverlessQA'] }, () }); describe('Overview page with no data', { tags: '@brokenInServerless' }, () => { - before(() => { - cy.task('esArchiverUnload', 'auditbeat'); - }); - after(() => { - cy.task('esArchiverLoad', { archiveName: 'auditbeat' }); - }); - it('Splash screen should be here', () => { login(); visitWithTimeRange(OVERVIEW_URL); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/alerts_cell_actions.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/alerts_cell_actions.cy.ts index 84cfcc8b52aad0..911b870d083474 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/alerts_cell_actions.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/alerts_cell_actions.cy.ts @@ -105,7 +105,7 @@ describe('Alerts cell actions', { tags: ['@ess', '@serverless'] }, () => { .first() .invoke('text') .then((severityVal) => { - scrollAlertTableColumnIntoView(ALERT_TABLE_SEVERITY_VALUES); + scrollAlertTableColumnIntoView(ALERT_TABLE_SEVERITY_HEADER); addAlertPropertyToTimeline(ALERT_TABLE_SEVERITY_VALUES, 0); openActiveTimeline(); cy.get(PROVIDER_BADGE) @@ -138,7 +138,7 @@ describe('Alerts cell actions', { tags: ['@ess', '@serverless'] }, () => { .first() .invoke('text') .then(() => { - scrollAlertTableColumnIntoView(ALERT_TABLE_SEVERITY_VALUES); + scrollAlertTableColumnIntoView(ALERT_TABLE_SEVERITY_HEADER); showTopNAlertProperty(ALERT_TABLE_SEVERITY_VALUES, 0); cy.get(SHOW_TOP_N_HEADER).first().should('have.text', `Top kibana.alert.severity`); }); @@ -157,7 +157,7 @@ describe('Alerts cell actions', { tags: ['@ess', '@serverless'] }, () => { .first() .invoke('text') .then(() => { - scrollAlertTableColumnIntoView(ALERT_TABLE_SEVERITY_VALUES); + scrollAlertTableColumnIntoView(ALERT_TABLE_SEVERITY_HEADER); cy.window().then((win) => { cy.stub(win, 'prompt').returns('DISABLED WINDOW PROMPT'); }); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/alerts_charts.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/alerts_charts.cy.ts index 18684358255971..426193bbf72ba4 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/alerts_charts.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/alerts_charts.cy.ts @@ -38,14 +38,14 @@ describe( }); it('Filter in/out should add a filter to KQL bar', function () { - const expectedNumberOfAlerts = 2; + const expectedNumberOfAlerts = 1; clickAlertsHistogramLegend(); clickAlertsHistogramLegendFilterFor(ruleConfigs.name); cy.get(GLOBAL_SEARCH_BAR_FILTER_ITEM).should( 'have.text', `kibana.alert.rule.name: ${ruleConfigs.name}` ); - cy.get(ALERTS_COUNT).should('have.text', `${expectedNumberOfAlerts} alerts`); + cy.get(ALERTS_COUNT).should('have.text', `${expectedNumberOfAlerts} alert`); clickAlertsHistogramLegend(); clickAlertsHistogramLegendFilterOut(ruleConfigs.name); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/alerts_details.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/alerts_details.cy.ts index ee51f507e37c77..9ab0d075694218 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/alerts_details.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/alerts_details.cy.ts @@ -54,8 +54,8 @@ describe('Alert details flyout', { tags: ['@ess', '@serverless'] }, () => { it('should update the table when status of the alert is updated', () => { cy.get(OVERVIEW_RULE).should('be.visible'); - cy.get(ALERTS_TABLE_COUNT).should('have.text', '2 alerts'); - cy.get(ALERT_SUMMARY_SEVERITY_DONUT_CHART).should('contain.text', '2alerts'); + cy.get(ALERTS_TABLE_COUNT).should('have.text', '1 alert'); + cy.get(ALERT_SUMMARY_SEVERITY_DONUT_CHART).should('contain.text', '1alert'); expandFirstAlert(); changeAlertStatusTo('acknowledged'); cy.get(ALERTS_TABLE_COUNT).should('have.text', '1 alert'); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/building_block_alerts.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/building_block_alerts.cy.ts index d913d1701c2c82..718feeca31533b 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/building_block_alerts.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/building_block_alerts.cy.ts @@ -19,11 +19,11 @@ const EXPECTED_NUMBER_OF_ALERTS = 5; describe('Alerts generated by building block rules', { tags: ['@ess', '@serverless'] }, () => { before(() => { - cy.task('esArchiverLoad', { archiveName: 'auditbeat_big' }); + cy.task('esArchiverLoad', { archiveName: 'auditbeat_multiple' }); }); after(() => { - cy.task('esArchiverUnload', 'auditbeat_big'); + cy.task('esArchiverUnload', 'auditbeat_multiple'); }); beforeEach(() => { diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/changing_alert_status.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/changing_alert_status.cy.ts index 3ee54dd437942e..f06fad0cd43eed 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/changing_alert_status.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/changing_alert_status.cy.ts @@ -42,11 +42,11 @@ import { ALERTS_URL } from '../../../urls/navigation'; // Iusse tracked in: https://github.com/elastic/kibana/issues/167809 describe('Changing alert status', { tags: ['@ess', '@brokenInServerless'] }, () => { before(() => { - cy.task('esArchiverLoad', { archiveName: 'auditbeat_big' }); + cy.task('esArchiverLoad', { archiveName: 'auditbeat_multiple' }); }); after(() => { - cy.task('esArchiverUnload', 'auditbeat_big'); + cy.task('esArchiverUnload', 'auditbeat_multiple'); }); context('Opening alerts', () => { diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/detection_page_filters.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/detection_page_filters.cy.ts index 9af8f133f5c7a0..dd29284f6c0ce7 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/detection_page_filters.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/detection_page_filters.cy.ts @@ -51,6 +51,7 @@ import { import { TOASTER } from '../../../screens/alerts_detection_rules'; import { setEndDate, setStartDate } from '../../../tasks/date_picker'; import { fillAddFilterForm, openAddFilterPopover } from '../../../tasks/search_bar'; +import { deleteAlertsAndRules } from '../../../tasks/common'; const customFilters = [ { @@ -106,17 +107,13 @@ const assertFilterControlsWithFilterObject = ( }); }; -// Failing: See https://github.com/elastic/kibana/issues/167914 -describe.skip(`Detections : Page Filters`, { tags: ['@ess', '@serverless'] }, () => { - before(() => { - createRule(getNewRule({ rule_id: 'custom_rule_filters' })); - }); - +describe(`Detections : Page Filters`, { tags: ['@ess', '@serverless'] }, () => { beforeEach(() => { + deleteAlertsAndRules(); + createRule(getNewRule({ rule_id: 'custom_rule_filters' })); login(); visitWithTimeRange(ALERTS_URL); waitForAlerts(); - resetFilters(); }); it('Default page filters are populated when nothing is provided in the URL', () => { @@ -124,16 +121,6 @@ describe.skip(`Detections : Page Filters`, { tags: ['@ess', '@serverless'] }, () }); context('Alert Page Filters Customization ', () => { - beforeEach(() => { - login(); - visitWithTimeRange(ALERTS_URL); - waitForAlerts(); - }); - - afterEach(() => { - resetFilters(); - }); - it('should be able to delete Controls', () => { waitForPageFilters(); editFilterGroupControls(); @@ -234,10 +221,6 @@ describe.skip(`Detections : Page Filters`, { tags: ['@ess', '@serverless'] }, () }); context('with data modificiation', () => { - after(() => { - createRule(getNewRule({ rule_id: 'custom_rule_filters' })); - }); - it(`Alert list is updated when the alerts are updated`, () => { // mark status of one alert to be acknowledged selectCountTable(); @@ -252,7 +235,7 @@ describe.skip(`Detections : Page Filters`, { tags: ['@ess', '@serverless'] }, () cy.get(ALERTS_COUNT) .invoke('text') .should((newAlertCount) => { - expect(newAlertCount.split(' ')[0]).eq(String(parseInt(originalAlertCount, 10) - 1)); + expect(newAlertCount.split(' ')[0]).eq(String(parseInt(originalAlertCount, 10))); }); }); }); @@ -335,7 +318,7 @@ describe.skip(`Detections : Page Filters`, { tags: ['@ess', '@serverless'] }, () togglePageFilterPopover(0); cy.get(OPTION_SELECTABLE(0, 'open')).should('be.visible'); cy.get(OPTION_SELECTABLE(0, 'open')).should('contain.text', 'open'); - cy.get(OPTION_SELECTABLE(0, 'open')).get(OPTION_SELECTABLE_COUNT).should('have.text', 2); + cy.get(OPTION_SELECTABLE(0, 'open')).get(OPTION_SELECTABLE_COUNT).should('have.text', 1); }); it('should take kqlQuery into account', () => { diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_prevalence_tab.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_prevalence_tab.cy.ts index d0f12799c795a8..38fd4ffb7496ab 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_prevalence_tab.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_prevalence_tab.cy.ts @@ -70,7 +70,7 @@ describe( .and('contain.text', 'test'); cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_PREVALENCE_TABLE_ALERT_COUNT_CELL).should( 'contain.text', - 2 + 1 ); cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_PREVALENCE_TABLE_DOC_COUNT_CELL).should( 'contain.text', diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel.cy.ts index 950d804ae352c0..ed4476bcfe4509 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel.cy.ts @@ -8,20 +8,11 @@ import { upperFirst } from 'lodash'; import { - DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE_CREATE_BUTTON, - DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE_DESCRIPTION_INPUT, - DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE_NAME_INPUT, EXISTING_CASE_SELECT_BUTTON, VIEW_CASE_TOASTER_LINK, } from '../../../../screens/expandable_flyout/common'; -import { - createNewCaseFromCases, - expandFirstAlertExpandableFlyout, - navigateToAlertsPage, - navigateToCasesPage, -} from '../../../../tasks/expandable_flyout/common'; -import { ALERT_CHECKBOX } from '../../../../screens/alerts'; -import { CASE_DETAILS_PAGE_TITLE } from '../../../../screens/case_details'; +import { expandFirstAlertExpandableFlyout } from '../../../../tasks/expandable_flyout/common'; +import { ALERT_CHECKBOX, EMPTY_ALERT_TABLE } from '../../../../screens/alerts'; import { DOCUMENT_DETAILS_FLYOUT_COLLAPSE_DETAILS_BUTTON, DOCUMENT_DETAILS_FLYOUT_EXPAND_DETAILS_BUTTON, @@ -51,6 +42,7 @@ import { import { collapseDocumentDetailsExpandableFlyoutLeftSection, expandDocumentDetailsExpandableFlyoutLeftSection, + fillOutFormToCreateNewCase, openJsonTab, openTableTab, openTakeActionButton, @@ -65,8 +57,7 @@ import { getNewRule } from '../../../../objects/rule'; import { ALERTS_URL } from '../../../../urls/navigation'; import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; -// FLAKY: https://github.com/elastic/kibana/issues/168317 -describe.skip('Alert details expandable flyout right panel', () => { +describe('Alert details expandable flyout right panel', { tags: ['@ess', '@serverless'] }, () => { const rule = getNewRule(); beforeEach(() => { @@ -77,14 +68,14 @@ describe.skip('Alert details expandable flyout right panel', () => { waitForAlertsToPopulate(); }); - it('should display header and footer basics', { tags: ['@ess', '@serverless'] }, () => { + it('should display header and footer basics', () => { expandFirstAlertExpandableFlyout(); - cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_TITLE).should('be.visible').and('have.text', rule.name); + cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_TITLE).should('have.text', rule.name); - cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_STATUS).should('be.visible'); + cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_STATUS).should('have.text', 'open'); - cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_RISK_SCORE).should('be.visible'); + cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_RISK_SCORE).should('have.text', 'Risk score:'); cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_RISK_SCORE_VALUE) .should('be.visible') .and('have.text', rule.risk_score); @@ -95,72 +86,65 @@ describe.skip('Alert details expandable flyout right panel', () => { cy.log('Verify all 3 tabs are visible'); - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB).should('be.visible').and('have.text', 'Overview'); - cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB).should('be.visible').and('have.text', 'Table'); - cy.get(DOCUMENT_DETAILS_FLYOUT_JSON_TAB).should('be.visible').and('have.text', 'JSON'); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB) + .should('have.text', 'Overview') + .and('have.class', 'euiTab-isSelected'); + cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB) + .should('have.text', 'Table') + .and('not.have.class', 'euiTab-isSelected'); + cy.get(DOCUMENT_DETAILS_FLYOUT_JSON_TAB) + .should('have.text', 'JSON') + .and('not.have.class', 'euiTab-isSelected'); cy.log('Verify the expand/collapse button is visible and functionality works'); expandDocumentDetailsExpandableFlyoutLeftSection(); - cy.get(DOCUMENT_DETAILS_FLYOUT_COLLAPSE_DETAILS_BUTTON) - .should('be.visible') - .and('have.text', 'Collapse details'); + cy.get(DOCUMENT_DETAILS_FLYOUT_COLLAPSE_DETAILS_BUTTON).should('have.text', 'Collapse details'); collapseDocumentDetailsExpandableFlyoutLeftSection(); - cy.get(DOCUMENT_DETAILS_FLYOUT_EXPAND_DETAILS_BUTTON) - .should('be.visible') - .and('have.text', 'Expand details'); + cy.get(DOCUMENT_DETAILS_FLYOUT_EXPAND_DETAILS_BUTTON).should('have.text', 'Expand details'); cy.log('Verify the take action button is visible on all tabs'); - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER).scrollIntoView(); cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER).should('be.visible'); cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_TAKE_ACTION_BUTTON).should('be.visible'); openTableTab(); - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB).should('have.class', 'euiTab-isSelected'); cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER).should('be.visible'); cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_TAKE_ACTION_BUTTON).should('be.visible'); openJsonTab(); - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_JSON_TAB).should('have.class', 'euiTab-isSelected'); cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER).should('be.visible'); cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_TAKE_ACTION_BUTTON).should('be.visible'); }); // TODO this will change when add to existing case is improved // https://github.com/elastic/security-team/issues/6298 - it('should add to existing case', { tags: ['@ess', '@serverless'] }, () => { - navigateToCasesPage(); - createNewCaseFromCases(); - - cy.get(CASE_DETAILS_PAGE_TITLE).should('be.visible').and('have.text', 'case'); - navigateToAlertsPage(); + it('should add to existing case', () => { expandFirstAlertExpandableFlyout(); + openTakeActionButtonAndSelectItem(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE); + fillOutFormToCreateNewCase(); openTakeActionButtonAndSelectItem(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_EXISTING_CASE); cy.get(EXISTING_CASE_SELECT_BUTTON).should('be.visible').contains('Select').click(); + cy.get(VIEW_CASE_TOASTER_LINK).should('be.visible').and('contain.text', 'View case'); }); // TODO this will change when add to new case is improved // https://github.com/elastic/security-team/issues/6298 - it('should add to new case', { tags: ['@ess', '@serverless'] }, () => { + it('should add to new case', () => { expandFirstAlertExpandableFlyout(); openTakeActionButtonAndSelectItem(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE); - - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE_NAME_INPUT).type('case'); - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE_DESCRIPTION_INPUT).type( - 'case description' - ); - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE_CREATE_BUTTON).click(); + fillOutFormToCreateNewCase(); cy.get(VIEW_CASE_TOASTER_LINK).should('be.visible').and('contain.text', 'View case'); }); - // Issue reported int: https://github.com/elastic/kibana/issues/167809 - it('should mark as acknowledged', { tags: ['@ess', '@brokenInServerless'] }, () => { - cy.get(ALERT_CHECKBOX).should('have.length', 2); + it('should mark as acknowledged', () => { + cy.get(ALERT_CHECKBOX).should('have.length', 1); expandFirstAlertExpandableFlyout(); openTakeActionButtonAndSelectItem(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_MARK_AS_ACKNOWLEDGED); @@ -169,68 +153,62 @@ describe.skip('Alert details expandable flyout right panel', () => { // cy.get(KIBANA_TOAST) // .should('be.visible') // .and('have.text', 'Successfully marked 1 alert as acknowledged.'); - cy.get(ALERT_CHECKBOX).should('have.length', 1); + cy.get(EMPTY_ALERT_TABLE).should('exist'); }); - // Issue reported int: https://github.com/elastic/kibana/issues/167809 - it('should mark as closed', { tags: ['@ess', '@brokenInServerless'] }, () => { - cy.get(ALERT_CHECKBOX).should('have.length', 2); + it('should mark as closed', () => { + cy.get(ALERT_CHECKBOX).should('have.length', 1); expandFirstAlertExpandableFlyout(); openTakeActionButtonAndSelectItem(DOCUMENT_DETAILS_FLYOUT_FOOTER_MARK_AS_CLOSED); // TODO figure out how to verify the toasts pops up // cy.get(KIBANA_TOAST).should('be.visible').and('have.text', 'Successfully closed 1 alert.'); - cy.get(ALERT_CHECKBOX).should('have.length', 1); + cy.get(EMPTY_ALERT_TABLE).should('exist'); }); // these actions are now grouped together as we're not really testing their functionality but just the existence of the option in the dropdown - // Issue reported int: https://github.com/elastic/kibana/issues/167809 - it( - 'should test other action within take action dropdown', - { tags: ['@ess', '@brokenInServerless'] }, - () => { - expandFirstAlertExpandableFlyout(); - - cy.log('should add endpoint exception'); - - // TODO figure out why this option is disabled in Cypress but not running the app locally - // https://github.com/elastic/security-team/issues/6300 - openTakeActionButton(); - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_ENDPOINT_EXCEPTION).should('be.disabled'); - - cy.log('should add rule exception'); - - // TODO this isn't fully testing the add rule exception yet - // https://github.com/elastic/security-team/issues/6301 - selectTakeActionItem(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_RULE_EXCEPTION); - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_RULE_EXCEPTION_FLYOUT_HEADER).should('be.visible'); - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_RULE_EXCEPTION_FLYOUT_CANCEL_BUTTON) - .should('be.visible') - .click(); - - // cy.log('should isolate host'); - - // TODO figure out why isolate host isn't showing up in the dropdown - // https://github.com/elastic/security-team/issues/6302 - // openTakeActionButton(); - // cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ISOLATE_HOST).should('be.visible'); - - cy.log('should respond'); - - // TODO this will change when respond is improved - // https://github.com/elastic/security-team/issues/6303 - openTakeActionButton(); - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_RESPOND).should('be.disabled'); - - cy.log('should investigate in timeline'); - - selectTakeActionItem(DOCUMENT_DETAILS_FLYOUT_FOOTER_INVESTIGATE_IN_TIMELINE); - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_INVESTIGATE_IN_TIMELINE_SECTION) - .first() - .within(() => - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_INVESTIGATE_IN_TIMELINE_ENTRY).should('be.visible') - ); - } - ); + it('should test other action within take action dropdown', () => { + expandFirstAlertExpandableFlyout(); + + cy.log('should add endpoint exception'); + + // TODO figure out why this option is disabled in Cypress but not running the app locally + // https://github.com/elastic/security-team/issues/6300 + openTakeActionButton(); + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_ENDPOINT_EXCEPTION).should('be.disabled'); + + cy.log('should add rule exception'); + + // TODO this isn't fully testing the add rule exception yet + // https://github.com/elastic/security-team/issues/6301 + selectTakeActionItem(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_RULE_EXCEPTION); + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_RULE_EXCEPTION_FLYOUT_HEADER).should('exist'); + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_RULE_EXCEPTION_FLYOUT_CANCEL_BUTTON) + .should('be.visible') + .click(); + + // cy.log('should isolate host'); + + // TODO figure out why isolate host isn't showing up in the dropdown + // https://github.com/elastic/security-team/issues/6302 + // openTakeActionButton(); + // cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ISOLATE_HOST).should('be.visible'); + + cy.log('should respond'); + + // TODO this will change when respond is improved + // https://github.com/elastic/security-team/issues/6303 + openTakeActionButton(); + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_RESPOND).should('be.disabled'); + + cy.log('should investigate in timeline'); + + selectTakeActionItem(DOCUMENT_DETAILS_FLYOUT_FOOTER_INVESTIGATE_IN_TIMELINE); + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_INVESTIGATE_IN_TIMELINE_SECTION) + .first() + .within(() => + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_INVESTIGATE_IN_TIMELINE_ENTRY).should('exist') + ); + }); }); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/investigate_in_timeline.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/investigate_in_timeline.cy.ts index db3fad7b57b2d6..0570557d33f21d 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/investigate_in_timeline.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/investigate_in_timeline.cy.ts @@ -65,10 +65,12 @@ describe('Investigate in timeline', { tags: ['@ess', '@serverless'] }, () => { // Click on the last button that lets us investigate in timeline. // We expect this to be the `process.args` row. + cy.get(ALERT_FLYOUT).find(SUMMARY_VIEW_INVESTIGATE_IN_TIMELINE_BUTTON).eq(5).scrollIntoView(); cy.get(ALERT_FLYOUT) .find(SUMMARY_VIEW_INVESTIGATE_IN_TIMELINE_BUTTON) - .last() - .should('have.text', alertCount) + .eq(5) + .should('be.visible') + .and('have.text', alertCount) .click(); // Make sure a new timeline is created and opened diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/dasbhoards/detection_response.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/dasbhoards/detection_response.cy.ts index 694209bbc8b227..e19e4ca043b3c5 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/dasbhoards/detection_response.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/dasbhoards/detection_response.cy.ts @@ -31,6 +31,7 @@ import { import { QUERY_TAB_BUTTON, TIMELINE_DATA_PROVIDERS_CONTAINER } from '../../../screens/timeline'; import { waitForAlerts } from '../../../tasks/alerts'; import { createRule } from '../../../tasks/api_calls/rules'; +import { deleteAlertsAndRules } from '../../../tasks/common'; import { investigateDashboardItemInTimeline } from '../../../tasks/dashboards/common'; import { waitToNavigateAwayFrom } from '../../../tasks/kibana_navigation'; import { login } from '../../../tasks/login'; @@ -42,10 +43,10 @@ import { ALERTS_URL, DASHBOARDS_URL, DETECTION_AND_RESPONSE_URL } from '../../.. const TEST_USER_NAME = 'test'; const SIEM_KIBANA_HOST_NAME = 'siem-kibana'; -// FLAKY: https://github.com/elastic/kibana/issues/168772 -// FLAKY: https://github.com/elastic/kibana/issues/168771 -describe.skip('Detection response view', { tags: ['@ess', '@serverless'] }, () => { +describe('Detection response view', { tags: ['@ess', '@serverless'] }, () => { before(() => { + deleteAlertsAndRules(); + cy.task('esArchiverLoad', { archiveName: 'auditbeat_multiple' }); createRule(getNewRule()); }); @@ -67,8 +68,8 @@ describe.skip('Detection response view', { tags: ['@ess', '@serverless'] }, () = kqlSearch(`host.name : ${SIEM_KIBANA_HOST_NAME}{enter}`); cy.get(HOST_TABLE_ROW_TOTAL_ALERTS).should('have.length', 1); - cy.get(RULE_TABLE_ROW_TOTAL_ALERTS).should('have.text', 2); - cy.get(ALERTS_DONUT_CHART).first().should('include.text', '2Open'); + cy.get(RULE_TABLE_ROW_TOTAL_ALERTS).should('have.text', 1); + cy.get(ALERTS_DONUT_CHART).first().should('include.text', '1Open'); }); it(`filters out the users with KQL search bar query`, () => { @@ -83,8 +84,8 @@ describe.skip('Detection response view', { tags: ['@ess', '@serverless'] }, () = kqlSearch(`user.name : ${TEST_USER_NAME}{enter}`); cy.get(USER_TABLE_ROW_TOTAL_ALERTS).should('have.length', 1); - cy.get(RULE_TABLE_ROW_TOTAL_ALERTS).should('have.text', 2); - cy.get(ALERTS_DONUT_CHART).first().should('include.text', '2Open'); + cy.get(RULE_TABLE_ROW_TOTAL_ALERTS).should('have.text', 1); + cy.get(ALERTS_DONUT_CHART).first().should('include.text', '1Open'); }); }); @@ -229,7 +230,7 @@ describe.skip('Detection response view', { tags: ['@ess', '@serverless'] }, () = expect(url.pathname.endsWith(ALERTS_URL)).eq(true); }); waitForAlerts(); - cy.get(ALERTS_COUNT).should('be.visible').should('have.text', `${alertCount} alerts`); + cy.get(ALERTS_COUNT).should('be.visible').should('have.text', `${alertCount} alert`); cy.get(CONTROL_FRAMES).should('have.length', 2); cy.get(OPTION_LIST_LABELS).eq(0).should('have.text', `Status`); cy.get(OPTION_LIST_VALUES(0)).should('have.text', 'open1'); @@ -254,7 +255,7 @@ describe.skip('Detection response view', { tags: ['@ess', '@serverless'] }, () = cy.get(USER_TABLE_ROW_SEV(severityVal)).click(); waitToNavigateAwayFrom(DASHBOARDS_URL); waitForAlerts(); - cy.get(ALERTS_COUNT).should('be.visible').should('have.text', `${alertCount} alerts`); + cy.get(ALERTS_COUNT).should('be.visible').should('have.text', `${alertCount} alert`); cy.get(CONTROL_FRAMES).should('have.length', 3); cy.get(OPTION_LIST_LABELS).eq(0).should('have.text', `Status`); cy.get(OPTION_LIST_VALUES(0)).should('have.text', 'open1'); diff --git a/x-pack/test/security_solution_cypress/cypress/screens/common/filter_group.ts b/x-pack/test/security_solution_cypress/cypress/screens/common/filter_group.ts index a4c069c2e24676..0f037a9955726e 100644 --- a/x-pack/test/security_solution_cypress/cypress/screens/common/filter_group.ts +++ b/x-pack/test/security_solution_cypress/cypress/screens/common/filter_group.ts @@ -7,37 +7,22 @@ import { getDataTestSubjectSelector } from '../../helpers/common'; -export const FILTER_GROUP_LOADING = '[data-test-subj="filter-group__loading"]'; -export const FILTER_GROUP_ITEMS = '[data-test-subj="filter-group__items"]'; -export const FILTER_GROUP_CLEAR = '[data-test-subj="filter-group__clear"]'; - +export const CONTROL_GROUP = '[data-test-subj="controls-group"]'; export const CONTROL_FRAMES = '[data-test-subj="control-frame"]'; export const CONTROL_FRAME_TITLE = '[data-test-subj="control-frame-title"]'; -export const CONTROL_FRAME_DRAG_HANDLE = '.controlFrame__dragHandle'; - export const OPTION_LIST_LABELS = '.controlFrame__labelToolTip'; export const OPTION_LIST_VALUES = (idx: number) => `[data-test-subj="optionsList-control-${idx}"]`; export const OPTION_LIST_CLEAR_BTN = '.presentationUtil__floatingActions [aria-label="Clear"]'; -export const OPTION_LIST_NUMBER_OFF = '.euiFilterButton__notification'; - export const OPTION_LISTS_LOADING = '.optionsList--filterBtnWrapper .euiLoadingSpinner'; -export const OPTION_LISTS_EXISTS = '[data-test-subj="optionsList-control-selection-exists"]'; - -export const OPTION_LIST_ACTIVE_CLEAR_SELECTION = - '[data-test-subj="optionsList-control-clear-all-selections"]'; - export const OPTION_SELECTABLE = (popoverIndex: number, value: string) => `#control-popover-${popoverIndex} [data-test-subj="optionsList-control-selection-${value}"]`; -export const OPTION_IGNORED = (popoverIndex: number, value: string) => - `#control-popover-${popoverIndex} [data-test-subj="optionsList-control-ignored-selection-${value}"]`; - export const OPTION_SELECTABLE_COUNT = getDataTestSubjectSelector( 'optionsList-document-count-badge' ); @@ -61,8 +46,6 @@ export const DETECTION_PAGE_FILTER_GROUP_RESET_BUTTON = export const FILTER_GROUP_CONTEXT_EDIT_CONTROLS = '[data-test-subj="filter-group__context--edit"]'; -export const FILTER_GROUP_CONTEXT_SAVE_CONTROLS = '[data-test-subj="filter-group__context--save"]'; - export const FILTER_GROUP_CONTEXT_DISCARD_CHANGES = '[data-test-subj="filter-group__context--discard"]'; @@ -70,10 +53,6 @@ export const FILTER_GROUP_ADD_CONTROL = '[data-test-subj="filter-group__add-cont export const FILTER_GROUP_SAVE_CHANGES = '[data-test-subj="filter-group__save"]'; -export const FILTER_GROUP_DISCARD_CHANGES = '[data-test-subj="filter-group__discard"]'; - -export const FILTER_GROUP_SAVE_CHANGES_POPOVER = '[data-test-subj="filter-group__save-popover"]'; - export const FILTER_GROUP_EDIT_CONTROLS_PANEL = '[data-test-subj="control-editor-flyout"]'; export const FILTER_GROUP_EDIT_CONTROL_PANEL_ITEMS = { diff --git a/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/common.ts b/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/common.ts index bc0119464374cb..f3c0df5cf8f31c 100644 --- a/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/common.ts +++ b/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/common.ts @@ -10,18 +10,13 @@ import { getDataTestSubjectSelectorStartWith, } from '../../helpers/common'; -export const KIBANA_NAVBAR_ALERTS_PAGE = getDataTestSubjectSelector( - 'solutionSideNavItemLink-alerts' -); -export const KIBANA_NAVBAR_CASES_PAGE = getDataTestSubjectSelector('solutionSideNavItemLink-cases'); export const VIEW_CASE_TOASTER_LINK = getDataTestSubjectSelector('toaster-content-case-view-link'); -export const CREATE_CASE_BUTTON = `[data-test-subj="createNewCaseBtn"]`; export const NEW_CASE_NAME_INPUT = `[data-test-subj="input"][aria-describedby="caseTitle"]`; export const NEW_CASE_DESCRIPTION_INPUT = getDataTestSubjectSelector('euiMarkdownEditorTextArea'); -export const NEW_CASE_CREATE_BUTTON = getDataTestSubjectSelector('create-case-submit'); export const EXISTING_CASE_SELECT_BUTTON = getDataTestSubjectSelectorStartWith('cases-table-row-select-'); export const DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE_NAME_INPUT = NEW_CASE_NAME_INPUT; export const DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE_DESCRIPTION_INPUT = NEW_CASE_DESCRIPTION_INPUT; -export const DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE_CREATE_BUTTON = NEW_CASE_CREATE_BUTTON; +export const DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE_CREATE_BUTTON = + getDataTestSubjectSelector('create-case-submit'); diff --git a/x-pack/test/security_solution_cypress/cypress/screens/overview.ts b/x-pack/test/security_solution_cypress/cypress/screens/overview.ts index 442fcf621fc95f..37b95f6ba6f59e 100644 --- a/x-pack/test/security_solution_cypress/cypress/screens/overview.ts +++ b/x-pack/test/security_solution_cypress/cypress/screens/overview.ts @@ -55,7 +55,7 @@ const STAT_PACKAGE = { domId: '[data-test-subj="host-stat-auditbeatPackage"]', }; const STAT_PROCESS = { - value: '2', + value: '1', domId: '[data-test-subj="host-stat-auditbeatProcess"]', }; const STAT_USER = { diff --git a/x-pack/test/security_solution_cypress/cypress/support/e2e.ts b/x-pack/test/security_solution_cypress/cypress/support/e2e.ts index eb3488178485f1..b0961b77a2bba9 100644 --- a/x-pack/test/security_solution_cypress/cypress/support/e2e.ts +++ b/x-pack/test/security_solution_cypress/cypress/support/e2e.ts @@ -16,7 +16,7 @@ import { setupUsers } from './setup_users'; import { CLOUD_SERVERLESS, IS_SERVERLESS } from '../env_var_names_constants'; before(() => { - cy.task('esArchiverLoad', { archiveName: 'auditbeat' }); + cy.task('esArchiverLoad', { archiveName: 'auditbeat_single' }); }); if (!Cypress.env(IS_SERVERLESS) && !Cypress.env(CLOUD_SERVERLESS)) { diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/alerts.ts b/x-pack/test/security_solution_cypress/cypress/tasks/alerts.ts index 23b5d106cb6cf0..cc1a06d3545dee 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/alerts.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/alerts.ts @@ -75,6 +75,7 @@ import { OPTION_LIST_VALUES, OPTION_LIST_CLEAR_BTN, OPTION_SELECTABLE, + CONTROL_GROUP, } from '../screens/common/filter_group'; import { LOADING_SPINNER } from '../screens/common/page'; import { ALERTS_URL } from '../urls/navigation'; @@ -189,7 +190,7 @@ export const closePageFilterPopover = (filterIndex: number) => { }; export const clearAllSelections = (filterIndex: number) => { - cy.scrollTo('top'); + cy.get(CONTROL_GROUP).scrollIntoView(); recurse( () => { cy.get(CONTROL_FRAME_TITLE).eq(filterIndex).realHover(); diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/exceptions.ts b/x-pack/test/security_solution_cypress/cypress/tasks/exceptions.ts index 00d6c2d405b872..4ed5ff5ef2f8da 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/exceptions.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/exceptions.ts @@ -237,8 +237,9 @@ export const addExceptionComment = (comment: string) => { export const addExceptionHugeComment = (comment: string) => { cy.get(EXCEPTION_COMMENTS_ACCORDION_BTN).click(); + cy.get(EXCEPTION_COMMENT_TEXT_AREA).type(` {backspace}`); cy.get(EXCEPTION_COMMENT_TEXT_AREA).invoke('val', comment); - cy.get(EXCEPTION_COMMENT_TEXT_AREA).type(`!{backspace}`); + cy.get(EXCEPTION_COMMENT_TEXT_AREA).type(` {backspace}`); cy.get(EXCEPTION_COMMENT_TEXT_AREA).should('have.value', comment); }; diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/expandable_flyout/alert_details_right_panel.ts b/x-pack/test/security_solution_cypress/cypress/tasks/expandable_flyout/alert_details_right_panel.ts index 350c069639870c..11e1b6a0b0ef9b 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/expandable_flyout/alert_details_right_panel.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/expandable_flyout/alert_details_right_panel.ts @@ -13,9 +13,15 @@ import { DOCUMENT_DETAILS_FLYOUT_FOOTER_TAKE_ACTION_BUTTON, DOCUMENT_DETAILS_FLYOUT_FOOTER_TAKE_ACTION_BUTTON_DROPDOWN, DOCUMENT_DETAILS_FLYOUT_JSON_TAB, - DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB, DOCUMENT_DETAILS_FLYOUT_TABLE_TAB, } from '../../screens/expandable_flyout/alert_details_right_panel'; +import { + DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE_CREATE_BUTTON, + DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE_DESCRIPTION_INPUT, + DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE_NAME_INPUT, + VIEW_CASE_TOASTER_LINK, +} from '../../screens/expandable_flyout/common'; +import { TOASTER_CLOSE_ICON } from '../../screens/alerts_detection_rules'; /* Header */ @@ -35,14 +41,6 @@ export const collapseDocumentDetailsExpandableFlyoutLeftSection = () => { cy.get(DOCUMENT_DETAILS_FLYOUT_COLLAPSE_DETAILS_BUTTON).click(); }; -/** - * Open the Overview tab in the document details expandable flyout right section - */ -export const openOverviewTab = () => { - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB).scrollIntoView(); - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB).click(); -}; - /** * Open the Table tab in the document details expandable flyout right section */ @@ -95,3 +93,16 @@ export const selectTakeActionItem = (option: string) => { .should('be.visible') .within(() => cy.get(option).should('be.visible').click()); }; + +/** + * Create new case from the expandable flyout take action button + */ +export const fillOutFormToCreateNewCase = () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE_NAME_INPUT).type('case'); + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE_DESCRIPTION_INPUT).type('case description'); + + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE_CREATE_BUTTON).click(); + + cy.get(VIEW_CASE_TOASTER_LINK).should('be.visible'); + cy.get(TOASTER_CLOSE_ICON).should('be.visible').click(); +}; diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/expandable_flyout/common.ts b/x-pack/test/security_solution_cypress/cypress/tasks/expandable_flyout/common.ts index 424d723e732b5e..bc1d8e1cd8171f 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/expandable_flyout/common.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/expandable_flyout/common.ts @@ -8,33 +8,13 @@ import { EXPAND_ALERT_BTN } from '../../screens/alerts'; import { DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE } from '../../screens/expandable_flyout/alert_details_right_panel'; import { - CREATE_CASE_BUTTON, DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE_CREATE_BUTTON, DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE_DESCRIPTION_INPUT, DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE_NAME_INPUT, - KIBANA_NAVBAR_ALERTS_PAGE, - KIBANA_NAVBAR_CASES_PAGE, - NEW_CASE_CREATE_BUTTON, - NEW_CASE_DESCRIPTION_INPUT, - NEW_CASE_NAME_INPUT, VIEW_CASE_TOASTER_LINK, } from '../../screens/expandable_flyout/common'; import { openTakeActionButtonAndSelectItem } from './alert_details_right_panel'; -/** - * Navigates to the alerts page by clicking on the Kibana sidenav entry - */ -export const navigateToAlertsPage = () => { - cy.get(KIBANA_NAVBAR_ALERTS_PAGE).should('be.visible').click(); -}; - -/** - * Navigates to the cases page by clicking on the Kibana sidenav entry - */ -export const navigateToCasesPage = () => { - cy.get(KIBANA_NAVBAR_CASES_PAGE).click(); -}; - /** * Find the first alert row in the alerts table then click on the expand icon button to open the flyout */ @@ -42,18 +22,6 @@ export const expandFirstAlertExpandableFlyout = () => { cy.get(EXPAND_ALERT_BTN).first().click(); }; -/** - * Create a new case from the cases page - */ -export const createNewCaseFromCases = () => { - cy.get(CREATE_CASE_BUTTON).should('be.visible').click(); - cy.get(NEW_CASE_NAME_INPUT).should('be.visible').click(); - cy.get(NEW_CASE_NAME_INPUT).type('case'); - cy.get(NEW_CASE_DESCRIPTION_INPUT).should('be.visible').click(); - cy.get(NEW_CASE_DESCRIPTION_INPUT).type('case description'); - cy.get(NEW_CASE_CREATE_BUTTON).should('be.visible').click(); -}; - /** * create a new case from the expanded expandable flyout */ diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/login.ts b/x-pack/test/security_solution_cypress/cypress/tasks/login.ts index 26702a47c94277..07a91a903536bd 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/login.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/login.ts @@ -62,9 +62,7 @@ export interface User { } export const loginWithUser = (user: User): void => { - cy.session(user, () => { - loginWithUsernameAndPassword(user.username, user.password); - }); + loginWithUsernameAndPassword(user.username, user.password); }; /** @@ -129,9 +127,7 @@ const loginWithRole = (role: SecurityRoleName): void => { const password = 'changeme'; cy.log(`origin: ${Cypress.config().baseUrl}`); - cy.session(role, () => { - loginWithUsernameAndPassword(role, password); - }); + loginWithUsernameAndPassword(role, password); }; /** @@ -154,10 +150,7 @@ const loginViaEnvironmentCredentials = (): void => { const username = Cypress.env(ELASTICSEARCH_USERNAME); const password = Cypress.env(ELASTICSEARCH_PASSWORD); - - cy.session([username, password], () => { - loginWithUsernameAndPassword(username, password); - }); + loginWithUsernameAndPassword(username, password); }; /** diff --git a/x-pack/test/security_solution_cypress/es_archives/auditbeat_big/data.json b/x-pack/test/security_solution_cypress/es_archives/auditbeat_multiple/data.json similarity index 100% rename from x-pack/test/security_solution_cypress/es_archives/auditbeat_big/data.json rename to x-pack/test/security_solution_cypress/es_archives/auditbeat_multiple/data.json diff --git a/x-pack/test/security_solution_cypress/es_archives/auditbeat_big/mappings.json b/x-pack/test/security_solution_cypress/es_archives/auditbeat_multiple/mappings.json similarity index 100% rename from x-pack/test/security_solution_cypress/es_archives/auditbeat_big/mappings.json rename to x-pack/test/security_solution_cypress/es_archives/auditbeat_multiple/mappings.json diff --git a/x-pack/test/security_solution_cypress/es_archives/auditbeat/data.json b/x-pack/test/security_solution_cypress/es_archives/auditbeat_single/data.json similarity index 50% rename from x-pack/test/security_solution_cypress/es_archives/auditbeat/data.json rename to x-pack/test/security_solution_cypress/es_archives/auditbeat_single/data.json index a4e42ac0335a92..cb9a035e8d19dd 100644 --- a/x-pack/test/security_solution_cypress/es_archives/auditbeat/data.json +++ b/x-pack/test/security_solution_cypress/es_archives/auditbeat_single/data.json @@ -1,126 +1,3 @@ -{ - "type": "doc", - "value": { - "id": "_aZE5nwBOpWiDweSth_F", - "index": "auditbeat-2022", - "source": { - "@timestamp" : "2022-03-04T19:41:33.045Z", - "host" : { - "hostname" : "test.local", - "architecture" : "x86_64", - "os" : { - "platform" : "darwin", - "version" : "10.16", - "family" : "darwin", - "name" : "Mac OS X", - "kernel" : "21.3.0", - "build" : "21D62", - "type" : "macos" - }, - "id" : "44426D67-79AB-547C-7777-440AB8F5DDD2", - "ip" : [ - "fe80::bade:48ff:fe00:1122", - "fe81::4ab:9565:1199:be3", - "192.168.5.175", - "fe80::40d7:d0ff:fe66:f55", - "fe81::40d8:d0ff:fe66:f55", - "fe82::c2c:6bdf:3307:dce0", - "fe83::5069:fcd5:e31c:7059", - "fe80::ce81:b2c:bd2c:69e", - "fe80::febc:bbc1:c517:827b", - "fe80::6d09:bee6:55a5:539d", - "fe80::c920:752e:1e0e:edc9", - "fe80::a4a:ca38:761f:83e2" - ], - "mac" : [ - "ad:df:48:00:11:22", - "a6:86:e7:ae:5a:b6", - "a9:83:e7:ae:5a:b6", - "43:d8:d0:66:0f:55", - "42:d8:d0:66:0f:57", - "82:70:c7:c2:3c:01", - "82:70:c6:c2:4c:00", - "82:76:a6:c2:3c:05", - "82:70:c6:b2:3c:04", - "82:71:a6:c2:3c:01" - ], - "name" : "siem-kibana" - }, - "agent" : { - "type" : "auditbeat", - "version" : "8.1.0", - "ephemeral_id" : "f6df090f-656a-4a79-a6a1-0c8671c9752d", - "id" : "0ebd469b-c164-4734-00e6-96d018098dc7", - "name" : "test.local" - }, - "event" : { - "module" : "system", - "dataset" : "process", - "kind" : "event", - "category" : [ - "process" - ], - "type" : [ - "start" - ], - "action" : "process_started" - }, - "destination": { - "port": 80 - }, - "process" : { - "start" : "2022-03-04T19:41:32.902Z", - "pid" : 30884, - "working_directory" : "/Users/test/security_solution", - "hash" : { - "sha1" : "ae2d46c38fa207efbea5fcecd6294eebbf5af00f" - }, - "parent" : { - "pid" : 777 - }, - "executable" : "/bin/zsh", - "name" : "zsh", - "args" : [ - "-zsh" - ], - "entity_id" : "q6pltOhTWlQx3BCD", - "entry_leader": { - "entity_id": "q6pltOhTWlQx3BCD", - "name": "fake entry", - "pid": 2342342 - } - }, - "message" : "Process zsh (PID: 27884) by user test STARTED", - "user" : { - "id" : "505", - "group" : { - "name" : "staff", - "id" : "20" - }, - "effective" : { - "id" : "505", - "group" : { - "id" : "20" - } - }, - "saved" : { - "id" : "505", - "group" : { - "id" : "20" - } - }, - "name" : "test" - }, - "service" : { - "type" : "system" - }, - "ecs" : { - "version" : "8.0.0" - } - } - } -} - { "type": "doc", "value": { diff --git a/x-pack/test/security_solution_cypress/es_archives/auditbeat/mappings.json b/x-pack/test/security_solution_cypress/es_archives/auditbeat_single/mappings.json similarity index 100% rename from x-pack/test/security_solution_cypress/es_archives/auditbeat/mappings.json rename to x-pack/test/security_solution_cypress/es_archives/auditbeat_single/mappings.json diff --git a/x-pack/test/security_solution_cypress/package.json b/x-pack/test/security_solution_cypress/package.json index 67125c9c55daae..0e34d7867d37a0 100644 --- a/x-pack/test/security_solution_cypress/package.json +++ b/x-pack/test/security_solution_cypress/package.json @@ -7,7 +7,7 @@ "scripts": { "cypress": "NODE_OPTIONS=--openssl-legacy-provider ../../../node_modules/.bin/cypress", "cypress:open:ess": "TZ=UTC NODE_OPTIONS=--openssl-legacy-provider node ../../plugins/security_solution/scripts/start_cypress_parallel open --spec './cypress/e2e/**/*.cy.ts' --config-file ../../test/security_solution_cypress/cypress/cypress.config.ts --ftr-config-file ../../test/security_solution_cypress/cli_config", - "cypress:run:ess": "yarn cypress:ess --spec './cypress/e2e/exceptions/shared_exception_lists_management/**/*.cy.ts'", + "cypress:run:ess": "yarn cypress:ess --spec './cypress/e2e/!(investigations|explore)/**/*.cy.ts'", "cypress:run:cases:ess": "yarn cypress:ess --spec './cypress/e2e/explore/cases/*.cy.ts'", "cypress:ess": "TZ=UTC NODE_OPTIONS=--openssl-legacy-provider node ../../plugins/security_solution/scripts/start_cypress_parallel run --config-file ../../test/security_solution_cypress/cypress/cypress_ci.config.ts --ftr-config-file ../../test/security_solution_cypress/cli_config", "cypress:run:respops:ess": "yarn cypress:ess --spec './cypress/e2e/(detection_response|exceptions)/**/*.cy.ts'", @@ -21,7 +21,7 @@ "cypress:cloud:serverless": "TZ=UTC NODE_OPTIONS=--openssl-legacy-provider NODE_TLS_REJECT_UNAUTHORIZED=0 ../../../node_modules/.bin/cypress", "cypress:open:cloud:serverless": "yarn cypress:cloud:serverless open --config-file ./cypress/cypress_serverless.config.ts --env CLOUD_SERVERLESS=true", "cypress:open:serverless": "yarn cypress:serverless open --config-file ../../test/security_solution_cypress/cypress/cypress_serverless.config.ts --spec './cypress/e2e/**/*.cy.ts'", - "cypress:run:serverless": "yarn cypress:serverless --spec './cypress/e2e/exceptions/shared_exception_lists_management/**/*.cy.ts'", + "cypress:run:serverless": "yarn cypress:serverless --spec './cypress/e2e/!(investigations|explore)/**/*.cy.ts'", "cypress:run:cloud:serverless": "yarn cypress:cloud:serverless run --config-file ./cypress/cypress_ci_serverless.config.ts --env CLOUD_SERVERLESS=true", "cypress:investigations:run:serverless": "yarn cypress:serverless --spec './cypress/e2e/investigations/**/*.cy.ts'", "cypress:explore:run:serverless": "yarn cypress:serverless --spec './cypress/e2e/explore/**/*.cy.ts'", diff --git a/x-pack/test/security_solution_endpoint/config.base.ts b/x-pack/test/security_solution_endpoint/config.base.ts index ebfce1ab4db0c4..d75458a4c581d9 100644 --- a/x-pack/test/security_solution_endpoint/config.base.ts +++ b/x-pack/test/security_solution_endpoint/config.base.ts @@ -14,7 +14,10 @@ import { } from '../security_solution_endpoint_api_int/registry'; import type { TargetTags } from './target_tags'; -const SUITE_TAGS: Record = { +export const SUITE_TAGS: Record< + 'ess' | 'serverless', + { include: TargetTags[]; exclude: TargetTags[] } +> = { ess: { include: ['@ess'], exclude: ['@skipInEss'], diff --git a/x-pack/test/security_solution_endpoint/services/index.ts b/x-pack/test/security_solution_endpoint/services/index.ts index b2c52ada028b07..5ba317dfaea3e7 100644 --- a/x-pack/test/security_solution_endpoint/services/index.ts +++ b/x-pack/test/security_solution_endpoint/services/index.ts @@ -13,7 +13,10 @@ import { TimelineTestService } from '../../security_solution_ftr/services/timeli import { DetectionsTestService } from '../../security_solution_ftr/services/detections'; import { EndpointPolicyTestResourcesProvider } from './endpoint_policy'; import { EndpointArtifactsTestResources } from './endpoint_artifacts'; -import { KibanaSupertestWithCertProvider } from './supertest_with_cert'; +import { + KibanaSupertestWithCertProvider, + KibanaSupertestWithCertWithoutAuthProvider, +} from './supertest_with_cert'; export const services = { ...xPackFunctionalServices, @@ -31,4 +34,5 @@ export const svlServices = { ...services, supertest: KibanaSupertestWithCertProvider, + supertestWithoutAuth: KibanaSupertestWithCertWithoutAuthProvider, }; diff --git a/x-pack/test/security_solution_endpoint/services/supertest_with_cert.ts b/x-pack/test/security_solution_endpoint/services/supertest_with_cert.ts index e06e144a1667fb..a23d850e3799bd 100644 --- a/x-pack/test/security_solution_endpoint/services/supertest_with_cert.ts +++ b/x-pack/test/security_solution_endpoint/services/supertest_with_cert.ts @@ -15,3 +15,11 @@ export function KibanaSupertestWithCertProvider({ getService }: FtrProviderConte return supertest.agent(kibanaServerUrl, { ca }); } + +export function KibanaSupertestWithCertWithoutAuthProvider({ getService }: FtrProviderContext) { + const config = getService('config'); + const kibanaServerUrl = formatUrl({ ...config.get('servers.kibana'), auth: false }); + const ca = config.get('servers.kibana').certificateAuthorities; + + return supertest.agent(kibanaServerUrl, { ca }); +} diff --git a/x-pack/test/security_solution_endpoint/target_tags.ts b/x-pack/test/security_solution_endpoint/target_tags.ts index dd71736e95ee56..4fd76a0173e21d 100644 --- a/x-pack/test/security_solution_endpoint/target_tags.ts +++ b/x-pack/test/security_solution_endpoint/target_tags.ts @@ -5,13 +5,44 @@ * 2.0. */ -export type TargetTags = - | '@ess' - | '@skipInEss' - | '@serverless' - | '@skipInServerless' - | '@brokenInServerless'; - -export const targetTags = (thisSuite: Mocha.Suite, tags: TargetTags[]) => { +import expect from '@kbn/expect'; + +const TARGET_TAGS = [ + '@ess', + '@skipInEss', + '@serverless', + '@skipInServerless', + '@brokenInServerless', +] as const; + +export type TargetTags = typeof TARGET_TAGS[number]; + +export function targetTags(thisSuite: Mocha.Suite, tags: TargetTags[]) { + // @ts-ignore: _tags is not publicly visible + const existingTags = (thisSuite._tags as string[]) ?? []; + const existingTargetTags = existingTags.filter((tag) => TARGET_TAGS.includes(tag as TargetTags)); + + if (existingTargetTags.length > 0) { + return expect().fail(` + + ⚠️ ERROR in \`${targetTags.name}()\`: the passed suite already has target tags. + + Suite name: ${thisSuite.title} + Existing tags: ${existingTargetTags.join(', ')} + New tags: ${tags.join(', ')} + + 💡 This can happen if you call \`${targetTags.name}()\` twice in the same block, or + → from the inside of an arrow function + → which is passed to a \`describe()\` block + → which is somewhere inside \`${thisSuite.title}\`. + + ☝️ Correct usage: + describe('must receive a regular function', function () { + ${targetTags.name}(this, ['@serverless']); + }) + + `); + } + thisSuite.tags(tags); -}; +} diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_artifacts/blocklists.ts b/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_artifacts/blocklists.ts index a5a5c109fe4407..5351cc22f628a7 100644 --- a/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_artifacts/blocklists.ts +++ b/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_artifacts/blocklists.ts @@ -13,6 +13,7 @@ import { GLOBAL_ARTIFACT_TAG, } from '@kbn/security-solution-plugin/common/endpoint/service/artifacts'; import { ExceptionsListItemGenerator } from '@kbn/security-solution-plugin/common/endpoint/data_generators/exceptions_list_item_generator'; +import { targetTags } from '../../../security_solution_endpoint/target_tags'; import { FtrProviderContext } from '../../ftr_provider_context'; import { PolicyTestResourceInfo } from '../../../security_solution_endpoint/services/endpoint_policy'; import { ArtifactTestData } from '../../../security_solution_endpoint/services/endpoint_artifacts'; @@ -24,7 +25,9 @@ export default function ({ getService }: FtrProviderContext) { const endpointPolicyTestResources = getService('endpointPolicyTestResources'); const endpointArtifactTestResources = getService('endpointArtifactTestResources'); - describe('Endpoint artifacts (via lists plugin): Blocklists', () => { + describe('Endpoint artifacts (via lists plugin): Blocklists', function () { + targetTags(this, ['@ess', '@serverless']); + let fleetEndpointPolicy: PolicyTestResourceInfo; before(async () => { @@ -155,7 +158,7 @@ export default function ({ getService }: FtrProviderContext) { body.entries[0].field = 'some.invalid.field'; await supertestWithoutAuth[blocklistApiCall.method](blocklistApiCall.path) - .auth(ROLE.analyst_hunter, 'changeme') + .auth(ROLE.endpoint_policy_manager, 'changeme') .set('kbn-xsrf', 'true') .send(body) .expect(400) @@ -176,7 +179,7 @@ export default function ({ getService }: FtrProviderContext) { ]; await supertestWithoutAuth[blocklistApiCall.method](blocklistApiCall.path) - .auth(ROLE.analyst_hunter, 'changeme') + .auth(ROLE.endpoint_policy_manager, 'changeme') .set('kbn-xsrf', 'true') .send(body) .expect(400) @@ -197,7 +200,7 @@ export default function ({ getService }: FtrProviderContext) { ]; await supertestWithoutAuth[blocklistApiCall.method](blocklistApiCall.path) - .auth(ROLE.analyst_hunter, 'changeme') + .auth(ROLE.endpoint_policy_manager, 'changeme') .set('kbn-xsrf', 'true') .send(body) .expect(400) @@ -224,7 +227,7 @@ export default function ({ getService }: FtrProviderContext) { ]; await supertestWithoutAuth[blocklistApiCall.method](blocklistApiCall.path) - .auth(ROLE.analyst_hunter, 'changeme') + .auth(ROLE.endpoint_policy_manager, 'changeme') .set('kbn-xsrf', 'true') .send(body) .expect(400) @@ -258,7 +261,7 @@ export default function ({ getService }: FtrProviderContext) { ]; await supertestWithoutAuth[blocklistApiCall.method](blocklistApiCall.path) - .auth(ROLE.analyst_hunter, 'changeme') + .auth(ROLE.endpoint_policy_manager, 'changeme') .set('kbn-xsrf', 'true') .send(body) .expect(400) @@ -272,7 +275,7 @@ export default function ({ getService }: FtrProviderContext) { body.os_types = ['linux', 'windows']; await supertestWithoutAuth[blocklistApiCall.method](blocklistApiCall.path) - .auth(ROLE.analyst_hunter, 'changeme') + .auth(ROLE.endpoint_policy_manager, 'changeme') .set('kbn-xsrf', 'true') .send(body) .expect(400) @@ -297,7 +300,7 @@ export default function ({ getService }: FtrProviderContext) { for (const blocklistApiCall of [...needsWritePrivilege, ...needsReadPrivilege]) { it(`should not error on [${blocklistApiCall.method}] - [${blocklistApiCall.info}]`, async () => { await supertestWithoutAuth[blocklistApiCall.method](blocklistApiCall.path) - .auth(ROLE.analyst_hunter, 'changeme') + .auth(ROLE.endpoint_policy_manager, 'changeme') .set('kbn-xsrf', 'true') .send(blocklistApiCall.getBody()) .expect(200); @@ -305,24 +308,23 @@ export default function ({ getService }: FtrProviderContext) { } }); - describe('and user has authorization to read blocklist', () => { + describe('and user has authorization to read blocklist', function () { + targetTags(this, ['@skipInServerless']); // no such role in serverless + for (const blocklistApiCall of [...blocklistApiCalls, ...needsWritePrivilege]) { it(`should error on [${blocklistApiCall.method}] - [${blocklistApiCall.info}]`, async () => { await supertestWithoutAuth[blocklistApiCall.method](blocklistApiCall.path) - .auth(ROLE.artifact_read_role, 'changeme') + .auth(ROLE.artifact_read_privileges, 'changeme') .set('kbn-xsrf', 'true') .send(blocklistApiCall.getBody()) - .expect(403, { - status_code: 403, - message: 'EndpointArtifactError: Endpoint authorization failure', - }); + .expect(403); }); } for (const blocklistApiCall of needsReadPrivilege) { it(`should not error on [${blocklistApiCall.method}] - [${blocklistApiCall.info}]`, async () => { await supertestWithoutAuth[blocklistApiCall.method](blocklistApiCall.path) - .auth(ROLE.artifact_read_role, 'changeme') + .auth(ROLE.artifact_read_privileges, 'changeme') .set('kbn-xsrf', 'true') .send(blocklistApiCall.getBody()) .expect(200); @@ -341,10 +343,7 @@ export default function ({ getService }: FtrProviderContext) { .auth(ROLE.t1_analyst, 'changeme') .set('kbn-xsrf', 'true') .send(blocklistApiCall.getBody()) - .expect(403, { - status_code: 403, - message: 'EndpointArtifactError: Endpoint authorization failure', - }); + .expect(403); }); } }); diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_artifacts/event_filters.ts b/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_artifacts/event_filters.ts index 8a00471665e048..5f5c75caa68f94 100644 --- a/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_artifacts/event_filters.ts +++ b/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_artifacts/event_filters.ts @@ -14,6 +14,7 @@ import { getImportExceptionsListSchemaMock, toNdJsonString, } from '@kbn/lists-plugin/common/schemas/request/import_exceptions_schema.mock'; +import { targetTags } from '../../../security_solution_endpoint/target_tags'; import { FtrProviderContext } from '../../ftr_provider_context'; import { PolicyTestResourceInfo } from '../../../security_solution_endpoint/services/endpoint_policy'; import { ArtifactTestData } from '../../../security_solution_endpoint/services/endpoint_artifacts'; @@ -25,7 +26,9 @@ export default function ({ getService }: FtrProviderContext) { const endpointPolicyTestResources = getService('endpointPolicyTestResources'); const endpointArtifactTestResources = getService('endpointArtifactTestResources'); - describe('Endpoint artifacts (via lists plugin): Event Filters', () => { + describe('Endpoint artifacts (via lists plugin): Event Filters', function () { + targetTags(this, ['@ess', '@serverless']); + let fleetEndpointPolicy: PolicyTestResourceInfo; before(async () => { @@ -182,7 +185,7 @@ export default function ({ getService }: FtrProviderContext) { const body = eventFilterApiCall.getBody({ os_types: ['linux', 'windows'] }); await supertestWithoutAuth[eventFilterApiCall.method](eventFilterApiCall.path) - .auth(ROLE.endpoint_security_policy_manager, 'changeme') + .auth(ROLE.endpoint_policy_manager, 'changeme') .set('kbn-xsrf', 'true') .send(body) .expect(400) @@ -197,7 +200,7 @@ export default function ({ getService }: FtrProviderContext) { // Using superuser there as we need custom license for this action await supertest[eventFilterApiCall.method](eventFilterApiCall.path) - .auth(ROLE.endpoint_security_policy_manager, 'changeme') + .auth(ROLE.endpoint_policy_manager, 'changeme') .set('kbn-xsrf', 'true') .send(body) .expect(400) @@ -210,7 +213,7 @@ export default function ({ getService }: FtrProviderContext) { // Using superuser here as we need custom license for this action await supertest[eventFilterApiCall.method](eventFilterApiCall.path) - .auth(ROLE.endpoint_security_policy_manager, 'changeme') + .auth(ROLE.endpoint_policy_manager, 'changeme') .set('kbn-xsrf', 'true') .send(body) .expect(200); @@ -222,7 +225,7 @@ export default function ({ getService }: FtrProviderContext) { for (const eventFilterApiCall of [...needsWritePrivilege, ...needsReadPrivilege]) { it(`should not error on [${eventFilterApiCall.method}] - [${eventFilterApiCall.info}]`, async () => { await supertestWithoutAuth[eventFilterApiCall.method](eventFilterApiCall.path) - .auth(ROLE.endpoint_security_policy_manager, 'changeme') + .auth(ROLE.endpoint_policy_manager, 'changeme') .set('kbn-xsrf', 'true') .send(eventFilterApiCall.getBody()) .expect(200); @@ -230,24 +233,23 @@ export default function ({ getService }: FtrProviderContext) { } }); - describe('and user has authorization to read event filters', () => { + describe('and user has authorization to read event filters', function () { + targetTags(this, ['@skipInServerless']); // no such role in serverless + for (const eventFilterApiCall of [...eventFilterCalls, ...needsWritePrivilege]) { it(`should error on [${eventFilterApiCall.method}] - [${eventFilterApiCall.info}]`, async () => { await supertestWithoutAuth[eventFilterApiCall.method](eventFilterApiCall.path) - .auth(ROLE.artifact_read_role, 'changeme') + .auth(ROLE.hunter, 'changeme') .set('kbn-xsrf', 'true') .send(eventFilterApiCall.getBody()) - .expect(403, { - status_code: 403, - message: 'EndpointArtifactError: Endpoint authorization failure', - }); + .expect(403); }); } for (const eventFilterApiCall of needsReadPrivilege) { it(`should not error on [${eventFilterApiCall.method}] - [${eventFilterApiCall.info}]`, async () => { await supertestWithoutAuth[eventFilterApiCall.method](eventFilterApiCall.path) - .auth(ROLE.artifact_read_role, 'changeme') + .auth(ROLE.hunter, 'changeme') .set('kbn-xsrf', 'true') .send(eventFilterApiCall.getBody()) .expect(200); @@ -266,10 +268,7 @@ export default function ({ getService }: FtrProviderContext) { .auth(ROLE.t1_analyst, 'changeme') .set('kbn-xsrf', 'true') .send(eventFilterApiCall.getBody()) - .expect(403, { - status_code: 403, - message: 'EndpointArtifactError: Endpoint authorization failure', - }); + .expect(403); }); } }); diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_artifacts/host_isolation_exceptions.ts b/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_artifacts/host_isolation_exceptions.ts index b22ff51ce8ed7f..09616f57a68d05 100644 --- a/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_artifacts/host_isolation_exceptions.ts +++ b/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_artifacts/host_isolation_exceptions.ts @@ -17,6 +17,7 @@ import { toNdJsonString, } from '@kbn/lists-plugin/common/schemas/request/import_exceptions_schema.mock'; import { ExceptionsListItemGenerator } from '@kbn/security-solution-plugin/common/endpoint/data_generators/exceptions_list_item_generator'; +import { targetTags } from '../../../security_solution_endpoint/target_tags'; import { FtrProviderContext } from '../../ftr_provider_context'; import { PolicyTestResourceInfo } from '../../../security_solution_endpoint/services/endpoint_policy'; import { ArtifactTestData } from '../../../security_solution_endpoint/services/endpoint_artifacts'; @@ -28,7 +29,9 @@ export default function ({ getService }: FtrProviderContext) { const endpointPolicyTestResources = getService('endpointPolicyTestResources'); const endpointArtifactTestResources = getService('endpointArtifactTestResources'); - describe('Endpoint Host Isolation Exceptions artifacts (via lists plugin)', () => { + describe('Endpoint Host Isolation Exceptions artifacts (via lists plugin)', function () { + targetTags(this, ['@ess', '@serverless']); + let fleetEndpointPolicy: PolicyTestResourceInfo; let hostIsolationExceptionData: ArtifactTestData; @@ -191,7 +194,7 @@ export default function ({ getService }: FtrProviderContext) { await supertestWithoutAuth[hostIsolationExceptionApiCall.method]( hostIsolationExceptionApiCall.path ) - .auth(ROLE.endpoint_security_policy_manager, 'changeme') + .auth(ROLE.endpoint_policy_manager, 'changeme') .set('kbn-xsrf', 'true') .send(body) .expect(400) @@ -207,7 +210,7 @@ export default function ({ getService }: FtrProviderContext) { await supertestWithoutAuth[hostIsolationExceptionApiCall.method]( hostIsolationExceptionApiCall.path ) - .auth(ROLE.endpoint_security_policy_manager, 'changeme') + .auth(ROLE.endpoint_policy_manager, 'changeme') .set('kbn-xsrf', 'true') .send(body) .expect(400) @@ -230,7 +233,7 @@ export default function ({ getService }: FtrProviderContext) { await supertestWithoutAuth[hostIsolationExceptionApiCall.method]( hostIsolationExceptionApiCall.path ) - .auth(ROLE.endpoint_security_policy_manager, 'changeme') + .auth(ROLE.endpoint_policy_manager, 'changeme') .set('kbn-xsrf', 'true') .send(body) .expect(400) @@ -246,7 +249,7 @@ export default function ({ getService }: FtrProviderContext) { await supertestWithoutAuth[hostIsolationExceptionApiCall.method]( hostIsolationExceptionApiCall.path ) - .auth(ROLE.endpoint_security_policy_manager, 'changeme') + .auth(ROLE.endpoint_policy_manager, 'changeme') .set('kbn-xsrf', 'true') .send(body) .expect(400) @@ -273,7 +276,7 @@ export default function ({ getService }: FtrProviderContext) { await supertestWithoutAuth[hostIsolationExceptionApiCall.method]( hostIsolationExceptionApiCall.path ) - .auth(ROLE.endpoint_security_policy_manager, 'changeme') + .auth(ROLE.endpoint_policy_manager, 'changeme') .set('kbn-xsrf', 'true') .send(hostIsolationExceptionApiCall.getBody()) .expect(200); @@ -281,7 +284,9 @@ export default function ({ getService }: FtrProviderContext) { } }); - describe('and user has authorization to read host isolation exceptions', () => { + describe('and user has authorization to read host isolation exceptions', function () { + targetTags(this, ['@skipInServerless']); // no such role in serverless + for (const hostIsolationExceptionApiCall of [ ...hostIsolationExceptionCalls, ...needsWritePrivilege, @@ -290,13 +295,10 @@ export default function ({ getService }: FtrProviderContext) { await supertestWithoutAuth[hostIsolationExceptionApiCall.method]( hostIsolationExceptionApiCall.path ) - .auth(ROLE.artifact_read_role, 'changeme') + .auth(ROLE.hunter, 'changeme') .set('kbn-xsrf', 'true') .send(hostIsolationExceptionApiCall.getBody()) - .expect(403, { - status_code: 403, - message: 'EndpointArtifactError: Endpoint authorization failure', - }); + .expect(403); }); } @@ -305,7 +307,7 @@ export default function ({ getService }: FtrProviderContext) { await supertestWithoutAuth[hostIsolationExceptionApiCall.method]( hostIsolationExceptionApiCall.path ) - .auth(ROLE.artifact_read_role, 'changeme') + .auth(ROLE.hunter, 'changeme') .set('kbn-xsrf', 'true') .send(hostIsolationExceptionApiCall.getBody()) .expect(200); @@ -326,10 +328,7 @@ export default function ({ getService }: FtrProviderContext) { .auth(ROLE.t1_analyst, 'changeme') .set('kbn-xsrf', 'true') .send(hostIsolationExceptionApiCall.getBody()) - .expect(403, { - status_code: 403, - message: 'EndpointArtifactError: Endpoint authorization failure', - }); + .expect(403); }); } }); diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_artifacts/trusted_apps.ts b/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_artifacts/trusted_apps.ts index 9841cf2adab9cc..fb844f40a8fbfe 100644 --- a/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_artifacts/trusted_apps.ts +++ b/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_artifacts/trusted_apps.ts @@ -13,6 +13,7 @@ import { GLOBAL_ARTIFACT_TAG, } from '@kbn/security-solution-plugin/common/endpoint/service/artifacts'; import { ExceptionsListItemGenerator } from '@kbn/security-solution-plugin/common/endpoint/data_generators/exceptions_list_item_generator'; +import { targetTags } from '../../../security_solution_endpoint/target_tags'; import { FtrProviderContext } from '../../ftr_provider_context'; import { PolicyTestResourceInfo } from '../../../security_solution_endpoint/services/endpoint_policy'; import { ArtifactTestData } from '../../../security_solution_endpoint/services/endpoint_artifacts'; @@ -24,7 +25,9 @@ export default function ({ getService }: FtrProviderContext) { const endpointPolicyTestResources = getService('endpointPolicyTestResources'); const endpointArtifactTestResources = getService('endpointArtifactTestResources'); - describe('Endpoint artifacts (via lists plugin): Trusted Applications', () => { + describe('Endpoint artifacts (via lists plugin): Trusted Applications', function () { + targetTags(this, ['@ess', '@serverless']); + let fleetEndpointPolicy: PolicyTestResourceInfo; before(async () => { @@ -155,7 +158,7 @@ export default function ({ getService }: FtrProviderContext) { body.entries[0].field = 'some.invalid.field'; await supertestWithoutAuth[trustedAppApiCall.method](trustedAppApiCall.path) - .auth(ROLE.endpoint_security_policy_manager, 'changeme') + .auth(ROLE.endpoint_policy_manager, 'changeme') .set('kbn-xsrf', 'true') .send(body) .expect(400) @@ -169,7 +172,7 @@ export default function ({ getService }: FtrProviderContext) { body.entries.push({ ...body.entries[0] }); await supertestWithoutAuth[trustedAppApiCall.method](trustedAppApiCall.path) - .auth(ROLE.endpoint_security_policy_manager, 'changeme') + .auth(ROLE.endpoint_policy_manager, 'changeme') .set('kbn-xsrf', 'true') .send(body) .expect(400) @@ -190,7 +193,7 @@ export default function ({ getService }: FtrProviderContext) { ]; await supertestWithoutAuth[trustedAppApiCall.method](trustedAppApiCall.path) - .auth(ROLE.endpoint_security_policy_manager, 'changeme') + .auth(ROLE.endpoint_policy_manager, 'changeme') .set('kbn-xsrf', 'true') .send(body) .expect(400) @@ -224,7 +227,7 @@ export default function ({ getService }: FtrProviderContext) { ]; await supertestWithoutAuth[trustedAppApiCall.method](trustedAppApiCall.path) - .auth(ROLE.endpoint_security_policy_manager, 'changeme') + .auth(ROLE.endpoint_policy_manager, 'changeme') .set('kbn-xsrf', 'true') .send(body) .expect(400) @@ -238,7 +241,7 @@ export default function ({ getService }: FtrProviderContext) { body.os_types = ['linux', 'windows']; await supertestWithoutAuth[trustedAppApiCall.method](trustedAppApiCall.path) - .auth(ROLE.endpoint_security_policy_manager, 'changeme') + .auth(ROLE.endpoint_policy_manager, 'changeme') .set('kbn-xsrf', 'true') .send(body) .expect(400) @@ -253,7 +256,7 @@ export default function ({ getService }: FtrProviderContext) { // Using superuser here as we need custom license for this action await supertest[trustedAppApiCall.method](trustedAppApiCall.path) - .auth(ROLE.endpoint_security_policy_manager, 'changeme') + .auth(ROLE.endpoint_policy_manager, 'changeme') .set('kbn-xsrf', 'true') .send(body) .expect(400) @@ -264,7 +267,7 @@ export default function ({ getService }: FtrProviderContext) { for (const trustedAppApiCall of [...needsWritePrivilege, ...needsReadPrivilege]) { it(`should not error on [${trustedAppApiCall.method}] - [${trustedAppApiCall.info}]`, async () => { await supertestWithoutAuth[trustedAppApiCall.method](trustedAppApiCall.path) - .auth(ROLE.endpoint_security_policy_manager, 'changeme') + .auth(ROLE.endpoint_policy_manager, 'changeme') .set('kbn-xsrf', 'true') .send(trustedAppApiCall.getBody()) .expect(200); @@ -272,24 +275,23 @@ export default function ({ getService }: FtrProviderContext) { } }); - describe('and user has authorization to read trusted apps', () => { + describe('and user has authorization to read trusted apps', function () { + targetTags(this, ['@skipInServerless']); // no such role in serverless + for (const trustedAppApiCall of [...trustedAppApiCalls, ...needsWritePrivilege]) { it(`should error on [${trustedAppApiCall.method}] - [${trustedAppApiCall.info}]`, async () => { await supertestWithoutAuth[trustedAppApiCall.method](trustedAppApiCall.path) - .auth(ROLE.artifact_read_role, 'changeme') + .auth(ROLE.hunter, 'changeme') .set('kbn-xsrf', 'true') .send(trustedAppApiCall.getBody()) - .expect(403, { - status_code: 403, - message: 'EndpointArtifactError: Endpoint authorization failure', - }); + .expect(403); }); } for (const trustedAppApiCall of needsReadPrivilege) { it(`should not error on [${trustedAppApiCall.method}] - [${trustedAppApiCall.info}]`, async () => { await supertestWithoutAuth[trustedAppApiCall.method](trustedAppApiCall.path) - .auth(ROLE.artifact_read_role, 'changeme') + .auth(ROLE.hunter, 'changeme') .set('kbn-xsrf', 'true') .send(trustedAppApiCall.getBody()) .expect(200); @@ -308,10 +310,7 @@ export default function ({ getService }: FtrProviderContext) { .auth(ROLE.t1_analyst, 'changeme') .set('kbn-xsrf', 'true') .send(trustedAppApiCall.getBody()) - .expect(403, { - status_code: 403, - message: 'EndpointArtifactError: Endpoint authorization failure', - }); + .expect(403); }); } }); diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_authz.ts b/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_authz.ts index ef242d887496f3..7434f46ca35be6 100644 --- a/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_authz.ts +++ b/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_authz.ts @@ -24,6 +24,7 @@ import { EXECUTE_ROUTE, } from '@kbn/security-solution-plugin/common/endpoint/constants'; import { IndexedHostsAndAlertsResponse } from '@kbn/security-solution-plugin/common/endpoint/index_data'; +import { targetTags } from '../../security_solution_endpoint/target_tags'; import { FtrProviderContext } from '../ftr_provider_context'; import { ROLE } from '../services/roles_users'; @@ -39,7 +40,9 @@ export default function ({ getService }: FtrProviderContext) { body: Record | undefined; } - describe('When attempting to call an endpoint api', () => { + describe('When attempting to call an endpoint api', function () { + targetTags(this, ['@ess', '@serverless']); + let indexedData: IndexedHostsAndAlertsResponse; let actionId = ''; let agentId = ''; @@ -246,7 +249,7 @@ export default function ({ getService }: FtrProviderContext) { apiListItem.path }]`, async () => { await supertestWithoutAuth[apiListItem.method](replacePathIds(apiListItem.path)) - .auth(ROLE.analyst_hunter, 'changeme') + .auth(ROLE.endpoint_operations_analyst, 'changeme') .set('kbn-xsrf', 'xxx') .send(apiListItem.body) .expect(403, { @@ -268,7 +271,7 @@ export default function ({ getService }: FtrProviderContext) { apiListItem.path }]`, async () => { await supertestWithoutAuth[apiListItem.method](replacePathIds(apiListItem.path)) - .auth(ROLE.analyst_hunter, 'changeme') + .auth(ROLE.endpoint_operations_analyst, 'changeme') .set('kbn-xsrf', 'xxx') .send(apiListItem.body) .expect(200); diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_response_actions/execute.ts b/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_response_actions/execute.ts index 9efd670c55db8e..f904539dae231a 100644 --- a/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_response_actions/execute.ts +++ b/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_response_actions/execute.ts @@ -8,6 +8,7 @@ import { wrapErrorAndRejectPromise } from '@kbn/security-solution-plugin/common/ import expect from '@kbn/expect'; import { EXECUTE_ROUTE } from '@kbn/security-solution-plugin/common/endpoint/constants'; import { IndexedHostsAndAlertsResponse } from '@kbn/security-solution-plugin/common/endpoint/index_data'; +import { targetTags } from '../../../security_solution_endpoint/target_tags'; import { FtrProviderContext } from '../../ftr_provider_context'; import { ROLE } from '../../services/roles_users'; @@ -15,7 +16,9 @@ export default function ({ getService }: FtrProviderContext) { const supertestWithoutAuth = getService('supertestWithoutAuth'); const endpointTestResources = getService('endpointTestResources'); - describe('Endpoint `execute` response action', () => { + describe('Endpoint `execute` response action', function () { + targetTags(this, ['@ess', '@serverless']); + let indexedData: IndexedHostsAndAlertsResponse; let agentId = ''; @@ -46,7 +49,7 @@ export default function ({ getService }: FtrProviderContext) { it('should error on invalid endpoint id', async () => { await supertestWithoutAuth .post(EXECUTE_ROUTE) - .auth(ROLE.response_actions_role, 'changeme') + .auth(ROLE.endpoint_operations_analyst, 'changeme') .set('kbn-xsrf', 'true') .set('Elastic-Api-Version', '2023-10-31') .send({ endpoint_ids: [' '], parameters: { command: 'ls -la' } }) @@ -60,7 +63,7 @@ export default function ({ getService }: FtrProviderContext) { it('should error on missing endpoint id', async () => { await supertestWithoutAuth .post(EXECUTE_ROUTE) - .auth(ROLE.response_actions_role, 'changeme') + .auth(ROLE.endpoint_operations_analyst, 'changeme') .set('kbn-xsrf', 'true') .set('Elastic-Api-Version', '2023-10-31') .send({ parameters: { command: 'ls -la' } }) @@ -75,7 +78,7 @@ export default function ({ getService }: FtrProviderContext) { it('should error on invalid `command` parameter', async () => { await supertestWithoutAuth .post(EXECUTE_ROUTE) - .auth(ROLE.response_actions_role, 'changeme') + .auth(ROLE.endpoint_operations_analyst, 'changeme') .set('kbn-xsrf', 'true') .set('Elastic-Api-Version', '2023-10-31') .send({ endpoint_ids: [agentId], parameters: { command: ' ' } }) @@ -89,7 +92,7 @@ export default function ({ getService }: FtrProviderContext) { it('should error on missing `command` parameter', async () => { await supertestWithoutAuth .post(EXECUTE_ROUTE) - .auth(ROLE.response_actions_role, 'changeme') + .auth(ROLE.endpoint_operations_analyst, 'changeme') .set('kbn-xsrf', 'true') .set('Elastic-Api-Version', '2023-10-31') .send({ endpoint_ids: [agentId] }) @@ -104,7 +107,7 @@ export default function ({ getService }: FtrProviderContext) { it('should error on invalid `timeout` parameter', async () => { await supertestWithoutAuth .post(EXECUTE_ROUTE) - .auth(ROLE.response_actions_role, 'changeme') + .auth(ROLE.endpoint_operations_analyst, 'changeme') .set('kbn-xsrf', 'true') .set('Elastic-Api-Version', '2023-10-31') .send({ endpoint_ids: [agentId], parameters: { command: 'ls -la', timeout: 'too' } }) @@ -121,7 +124,7 @@ export default function ({ getService }: FtrProviderContext) { body: { data }, } = await supertestWithoutAuth .post(EXECUTE_ROUTE) - .auth(ROLE.response_actions_role, 'changeme') + .auth(ROLE.endpoint_operations_analyst, 'changeme') .set('kbn-xsrf', 'true') .set('Elastic-Api-Version', '2023-10-31') .send({ endpoint_ids: [agentId], parameters: { command: 'ls -la' } }) @@ -137,7 +140,7 @@ export default function ({ getService }: FtrProviderContext) { body: { data }, } = await supertestWithoutAuth .post(EXECUTE_ROUTE) - .auth(ROLE.response_actions_role, 'changeme') + .auth(ROLE.endpoint_operations_analyst, 'changeme') .set('kbn-xsrf', 'true') .set('Elastic-Api-Version', '2023-10-31') .send({ endpoint_ids: [agentId], parameters: { command: 'ls -la', timeout: 2000 } }) diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/index.ts b/x-pack/test/security_solution_endpoint_api_int/apis/index.ts index 06d54f0aaac451..c0668612907a76 100644 --- a/x-pack/test/security_solution_endpoint_api_int/apis/index.ts +++ b/x-pack/test/security_solution_endpoint_api_int/apis/index.ts @@ -4,8 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - import { getRegistryUrl as getRegistryUrlFromIngest } from '@kbn/fleet-plugin/server'; +import { isServerlessKibanaFlavor } from '@kbn/security-solution-plugin/scripts/endpoint/common/stack_services'; import { FtrProviderContext } from '../ftr_provider_context'; import { getRegistryUrlFromTestEnv, isRegistryEnabled } from '../registry'; import { ROLE } from '../services/roles_users'; @@ -16,7 +16,7 @@ export default function endpointAPIIntegrationTests(providerContext: FtrProvider describe('Endpoint plugin', function () { const ingestManager = getService('ingestManager'); const rolesUsersProvider = getService('rolesUsersProvider'); - + const kbnClient = getService('kibanaServer'); const log = getService('log'); if (!isRegistryEnabled()) { @@ -34,16 +34,21 @@ export default function endpointAPIIntegrationTests(providerContext: FtrProvider log.warning(`Error setting up ingestManager: ${err}`); } - // create role/user - for (const role of roles) { - await rolesUsersProvider.createRole({ predefinedRole: role }); - await rolesUsersProvider.createUser({ name: role, roles: [role] }); + if (!(await isServerlessKibanaFlavor(kbnClient))) { + // create role/user + for (const role of roles) { + await rolesUsersProvider.createRole({ predefinedRole: role }); + await rolesUsersProvider.createUser({ name: role, roles: [role] }); + } } }); + after(async () => { - // delete role/user - await rolesUsersProvider.deleteUsers(roles); - await rolesUsersProvider.deleteRoles(roles); + if (!(await isServerlessKibanaFlavor(kbnClient))) { + // delete role/user + await rolesUsersProvider.deleteUsers(roles); + await rolesUsersProvider.deleteRoles(roles); + } }); loadTestFile(require.resolve('./resolver')); diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/metadata.ts b/x-pack/test/security_solution_endpoint_api_int/apis/metadata.ts index 17cc5906d7ba8d..3ac6c83938c14d 100644 --- a/x-pack/test/security_solution_endpoint_api_int/apis/metadata.ts +++ b/x-pack/test/security_solution_endpoint_api_int/apis/metadata.ts @@ -29,6 +29,7 @@ import { EndpointSortableField, MetadataListResponse, } from '@kbn/security-solution-plugin/common/endpoint/types'; +import { targetTags } from '../../security_solution_endpoint/target_tags'; import { generateAgentDocs, generateMetadataDocs } from './metadata.fixtures'; import { bulkIndex, @@ -47,7 +48,9 @@ export default function ({ getService }: FtrProviderContext) { const log = getService('log'); // Failing: See https://github.com/elastic/kibana/issues/151854 - describe.skip('test metadata apis', () => { + describe.skip('test metadata apis', function () { + targetTags(this, ['@ess', '@serverless']); + describe('list endpoints GET route', () => { const numberOfHostsInFixture = 2; let agent1Timestamp: number; @@ -415,10 +418,14 @@ export default function ({ getService }: FtrProviderContext) { }); it('should respond forbidden if no fleet access', async () => { + const config = getService('config'); + const ca = config.get('servers.kibana').certificateAuthorities; + await getService('supertestWithoutAuth') .get(METADATA_TRANSFORMS_STATUS_ROUTE) .set('kbn-xsrf', 'xxx') .set('Elastic-Api-Version', '2023-10-31') + .ca(ca) .expect(401); }); diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/package.ts b/x-pack/test/security_solution_endpoint_api_int/apis/package.ts index 9df605fa32a047..4b6fb578bbef81 100644 --- a/x-pack/test/security_solution_endpoint_api_int/apis/package.ts +++ b/x-pack/test/security_solution_endpoint_api_int/apis/package.ts @@ -15,6 +15,7 @@ import { EndpointDocGenerator, Event, } from '@kbn/security-solution-plugin/common/endpoint/generate_data'; +import { targetTags } from '../../security_solution_endpoint/target_tags'; import { FtrProviderContext } from '../ftr_provider_context'; import { InsertedEvents, processEventsIndex } from '../services/resolver'; import { deleteEventsStream } from './data_stream_helper'; @@ -70,7 +71,9 @@ export default function ({ getService }: FtrProviderContext) { }; // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/114885 - describe.skip('Endpoint package', () => { + describe.skip('Endpoint package', function () { + targetTags(this, ['@ess']); + describe('network processors', () => { let networkIndexData: InsertedEvents; diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/policy.ts b/x-pack/test/security_solution_endpoint_api_int/apis/policy.ts index 8b72a6c21bfe59..1fe5fbda42f196 100644 --- a/x-pack/test/security_solution_endpoint_api_int/apis/policy.ts +++ b/x-pack/test/security_solution_endpoint_api_int/apis/policy.ts @@ -6,13 +6,16 @@ */ import expect from '@kbn/expect'; +import { targetTags } from '../../security_solution_endpoint/target_tags'; import { FtrProviderContext } from '../ftr_provider_context'; import { deletePolicyStream } from './data_stream_helper'; export default function ({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const supertest = getService('supertest'); - describe('Endpoint policy api', () => { + describe('Endpoint policy api', function () { + targetTags(this, ['@ess', '@serverless']); + describe('GET /api/endpoint/policy_response', () => { before( async () => diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/resolver/entity.ts b/x-pack/test/security_solution_endpoint_api_int/apis/resolver/entity.ts index 268f67a30a918f..2a85d95f24c65a 100644 --- a/x-pack/test/security_solution_endpoint_api_int/apis/resolver/entity.ts +++ b/x-pack/test/security_solution_endpoint_api_int/apis/resolver/entity.ts @@ -8,13 +8,16 @@ import expect from '@kbn/expect'; import { eventsIndexPattern } from '@kbn/security-solution-plugin/common/endpoint/constants'; import { ResolverEntityIndex } from '@kbn/security-solution-plugin/common/endpoint/types'; +import { targetTags } from '../../../security_solution_endpoint/target_tags'; import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); - describe('Resolver tests for the entity route', () => { + describe('Resolver tests for the entity route', function () { + targetTags(this, ['@ess', '@serverless']); + describe('winlogbeat tests', () => { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/endpoint/resolver/winlogbeat'); @@ -27,9 +30,12 @@ export default function ({ getService }: FtrProviderContext) { it('returns a winlogbeat sysmon event when the event matches the schema correctly', async () => { // this id is from the es archive const _id = 'sysmon-event'; - const { body }: { body: ResolverEntityIndex } = await supertest.get( - `/api/endpoint/resolver/entity?_id=${_id}&indices=${eventsIndexPattern}&indices=winlogbeat-7.11.0-default` - ); + const { body }: { body: ResolverEntityIndex } = await supertest + .get( + `/api/endpoint/resolver/entity?_id=${_id}&indices=${eventsIndexPattern}&indices=winlogbeat-7.11.0-default` + ) + .set('x-elastic-internal-origin', 'xxx'); + expect(body).eql([ { name: 'winlogbeat', @@ -47,14 +53,20 @@ export default function ({ getService }: FtrProviderContext) { it('does not return a powershell event that has event.module set to powershell', async () => { // this id is from the es archive const _id = 'powershell-event'; - const { body }: { body: ResolverEntityIndex } = await supertest.get( - `/api/endpoint/resolver/entity?_id=${_id}&indices=${eventsIndexPattern}&indices=winlogbeat-7.11.0-default` - ); + const { body }: { body: ResolverEntityIndex } = await supertest + .get( + `/api/endpoint/resolver/entity?_id=${_id}&indices=${eventsIndexPattern}&indices=winlogbeat-7.11.0-default` + ) + .set('x-elastic-internal-origin', 'xxx'); + expect(body).to.be.empty(); }); }); - describe('signals index mapping tests', () => { + describe('signals index mapping tests', function () { + // illegal_argument_exception: unknown setting [index.lifecycle.name] in before + targetTags(this, ['@brokenInServerless']); + before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/endpoint/resolver/signals'); }); diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/resolver/entity_id.ts b/x-pack/test/security_solution_endpoint_api_int/apis/resolver/entity_id.ts index 6cf74092caf9ef..bd5bd0aeca023c 100644 --- a/x-pack/test/security_solution_endpoint_api_int/apis/resolver/entity_id.ts +++ b/x-pack/test/security_solution_endpoint_api_int/apis/resolver/entity_id.ts @@ -19,9 +19,11 @@ import { EndpointDocGenerator, Event, } from '@kbn/security-solution-plugin/common/endpoint/generate_data'; +import { targetTags } from '../../../security_solution_endpoint/target_tags'; import { FtrProviderContext } from '../../ftr_provider_context'; import { InsertedEvents, processEventsIndex } from '../../services/resolver'; import { createAncestryArray, schemaWithAncestry } from './common'; +import { HEADERS } from '../../headers'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); @@ -34,7 +36,9 @@ export default function ({ getService }: FtrProviderContext) { } }; - describe('Resolver handling of entity ids', () => { + describe('Resolver handling of entity ids', function () { + targetTags(this, ['@ess', '@serverless']); + describe('entity api', () => { let origin: Event; let genData: InsertedEvents; @@ -52,11 +56,14 @@ export default function ({ getService }: FtrProviderContext) { }); it('excludes events that have an empty entity_id field', async () => { - const { body }: { body: ResolverEntityIndex } = await supertest.get( - // using the same indices value here twice to force the query parameter to be an array - // for some reason using supertest's query() function doesn't construct a parsable array - `/api/endpoint/resolver/entity?_id=${genData.eventsInfo[0]._id}&indices=${eventsIndexPattern}&indices=${eventsIndexPattern}` - ); + const { body }: { body: ResolverEntityIndex } = await supertest + .get( + // using the same indices value here twice to force the query parameter to be an array + // for some reason using supertest's query() function doesn't construct a parsable array + `/api/endpoint/resolver/entity?_id=${genData.eventsInfo[0]._id}&indices=${eventsIndexPattern}&indices=${eventsIndexPattern}` + ) + .set(HEADERS); + expect(body).to.be.empty(); }); }); @@ -100,7 +107,7 @@ export default function ({ getService }: FtrProviderContext) { it('does not find children without a process entity_id', async () => { const { body }: { body: ResolverNode[] } = await supertest .post('/api/endpoint/resolver/tree') - .set('kbn-xsrf', 'xxx') + .set(HEADERS) .send({ descendants: 100, ancestors: 0, @@ -172,7 +179,7 @@ export default function ({ getService }: FtrProviderContext) { }; const { body }: { body: ResolverNode[] } = await supertest .post('/api/endpoint/resolver/tree') - .set('kbn-xsrf', 'xxx') + .set(HEADERS) .send({ descendants: 0, ancestors: 10, diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/resolver/events.ts b/x-pack/test/security_solution_endpoint_api_int/apis/resolver/events.ts index 10cac58533b247..2c1829d2d45017 100644 --- a/x-pack/test/security_solution_endpoint_api_int/apis/resolver/events.ts +++ b/x-pack/test/security_solution_endpoint_api_int/apis/resolver/events.ts @@ -18,9 +18,11 @@ import { Tree, RelatedEventCategory, } from '@kbn/security-solution-plugin/common/endpoint/generate_data'; +import { targetTags } from '../../../security_solution_endpoint/target_tags'; import { FtrProviderContext } from '../../ftr_provider_context'; import { Options, GeneratedTrees } from '../../services/resolver'; import { compareArrays } from './common'; +import { HEADERS } from '../../headers'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); @@ -48,7 +50,9 @@ export default function ({ getService }: FtrProviderContext) { ancestryArraySize: 2, }; - describe('event route', () => { + describe('event route', function () { + targetTags(this, ['@ess', '@serverless']); + let entityIDFilterArray: JsonObject[] | undefined; let entityIDFilter: string | undefined; before(async () => { @@ -77,7 +81,7 @@ export default function ({ getService }: FtrProviderContext) { }); const { body }: { body: ResolverPaginatedEvents } = await supertest .post(`/api/endpoint/resolver/events`) - .set('kbn-xsrf', 'xxx') + .set(HEADERS) .send({ filter, indexPatterns: [eventsIndexPattern], @@ -100,7 +104,7 @@ export default function ({ getService }: FtrProviderContext) { }); const { body }: { body: ResolverPaginatedEvents } = await supertest .post(`/api/endpoint/resolver/events`) - .set('kbn-xsrf', 'xxx') + .set(HEADERS) .send({ filter, indexPatterns: [eventsIndexPattern], @@ -117,7 +121,7 @@ export default function ({ getService }: FtrProviderContext) { it('should return related events for the root node', async () => { const { body }: { body: ResolverPaginatedEvents } = await supertest .post(`/api/endpoint/resolver/events`) - .set('kbn-xsrf', 'xxx') + .set(HEADERS) .send({ filter: entityIDFilter, indexPatterns: [eventsIndexPattern], @@ -143,7 +147,7 @@ export default function ({ getService }: FtrProviderContext) { }); const { body }: { body: ResolverPaginatedEvents } = await supertest .post(`/api/endpoint/resolver/events`) - .set('kbn-xsrf', 'xxx') + .set(HEADERS) .send({ filter, indexPatterns: [eventsIndexPattern], @@ -164,7 +168,7 @@ export default function ({ getService }: FtrProviderContext) { it('should return paginated results for the root node', async () => { let { body }: { body: ResolverPaginatedEvents } = await supertest .post(`/api/endpoint/resolver/events?limit=2`) - .set('kbn-xsrf', 'xxx') + .set(HEADERS) .send({ filter: entityIDFilter, indexPatterns: [eventsIndexPattern], @@ -180,7 +184,7 @@ export default function ({ getService }: FtrProviderContext) { ({ body } = await supertest .post(`/api/endpoint/resolver/events?limit=2&afterEvent=${body.nextEvent}`) - .set('kbn-xsrf', 'xxx') + .set(HEADERS) .send({ filter: entityIDFilter, indexPatterns: [eventsIndexPattern], @@ -196,7 +200,7 @@ export default function ({ getService }: FtrProviderContext) { ({ body } = await supertest .post(`/api/endpoint/resolver/events?limit=2&afterEvent=${body.nextEvent}`) - .set('kbn-xsrf', 'xxx') + .set(HEADERS) .send({ filter: entityIDFilter, indexPatterns: [eventsIndexPattern], @@ -213,7 +217,7 @@ export default function ({ getService }: FtrProviderContext) { it('should return the first page of information when the cursor is invalid', async () => { const { body }: { body: ResolverPaginatedEvents } = await supertest .post(`/api/endpoint/resolver/events?afterEvent=blah`) - .set('kbn-xsrf', 'xxx') + .set(HEADERS) .send({ filter: entityIDFilter, indexPatterns: [eventsIndexPattern], @@ -231,7 +235,7 @@ export default function ({ getService }: FtrProviderContext) { it('should sort the events in descending order', async () => { const { body }: { body: ResolverPaginatedEvents } = await supertest .post(`/api/endpoint/resolver/events`) - .set('kbn-xsrf', 'xxx') + .set(HEADERS) .send({ filter: entityIDFilter, indexPatterns: [eventsIndexPattern], @@ -258,7 +262,7 @@ export default function ({ getService }: FtrProviderContext) { const to = from; const { body }: { body: ResolverPaginatedEvents } = await supertest .post(`/api/endpoint/resolver/events`) - .set('kbn-xsrf', 'xxx') + .set(HEADERS) .send({ filter: entityIDFilter, indexPatterns: [eventsIndexPattern], @@ -276,7 +280,7 @@ export default function ({ getService }: FtrProviderContext) { it('should not find events when using an incorrect index pattern', async () => { const { body }: { body: ResolverPaginatedEvents } = await supertest .post(`/api/endpoint/resolver/events`) - .set('kbn-xsrf', 'xxx') + .set(HEADERS) .send({ filter: entityIDFilter, indexPatterns: ['doesnotexist-*'], @@ -295,7 +299,7 @@ export default function ({ getService }: FtrProviderContext) { expect(originParentID).to.not.be(''); const { body }: { body: ResolverPaginatedEvents } = await supertest .post(`/api/endpoint/resolver/events`) - .set('kbn-xsrf', 'xxx') + .set(HEADERS) .send({ filter: JSON.stringify({ bool: { @@ -323,7 +327,7 @@ export default function ({ getService }: FtrProviderContext) { let { body }: { body: ResolverPaginatedEvents } = await supertest .post(`/api/endpoint/resolver/events`) .query({ limit: 2 }) - .set('kbn-xsrf', 'xxx') + .set(HEADERS) .send({ filter: JSON.stringify({ bool: { @@ -346,7 +350,7 @@ export default function ({ getService }: FtrProviderContext) { ({ body } = await supertest .post(`/api/endpoint/resolver/events`) .query({ limit: 3, afterEvent: body.nextEvent }) - .set('kbn-xsrf', 'xxx') + .set(HEADERS) .send({ filter: JSON.stringify({ bool: { diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/resolver/tree.ts b/x-pack/test/security_solution_endpoint_api_int/apis/resolver/tree.ts index e4838b49f7ca15..c9602243e70e38 100644 --- a/x-pack/test/security_solution_endpoint_api_int/apis/resolver/tree.ts +++ b/x-pack/test/security_solution_endpoint_api_int/apis/resolver/tree.ts @@ -16,9 +16,11 @@ import { Tree, RelatedEventCategory, } from '@kbn/security-solution-plugin/common/endpoint/generate_data'; +import { targetTags } from '../../../security_solution_endpoint/target_tags'; import { FtrProviderContext } from '../../ftr_provider_context'; import { Options, GeneratedTrees } from '../../services/resolver'; import { schemaWithAncestry, schemaWithName, schemaWithoutAncestry, verifyTree } from './common'; +import { HEADERS } from '../../headers'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); @@ -45,7 +47,9 @@ export default function ({ getService }: FtrProviderContext) { ancestryArraySize: 2, }; - describe('Resolver tree', () => { + describe('Resolver tree', function () { + targetTags(this, ['@ess', '@serverless']); + before(async () => { resolverTrees = await resolver.createTrees(treeOptions); // we only requested a single alert so there's only 1 tree @@ -59,7 +63,7 @@ export default function ({ getService }: FtrProviderContext) { it('should return the correct ancestor nodes for the tree', async () => { const { body }: { body: ResolverNode[] } = await supertest .post('/api/endpoint/resolver/tree') - .set('kbn-xsrf', 'xxx') + .set(HEADERS) .send({ descendants: 0, descendantLevels: 0, @@ -84,7 +88,7 @@ export default function ({ getService }: FtrProviderContext) { it('should handle an invalid id', async () => { const { body }: { body: ResolverNode[] } = await supertest .post('/api/endpoint/resolver/tree') - .set('kbn-xsrf', 'xxx') + .set(HEADERS) .send({ descendants: 0, descendantLevels: 0, @@ -104,7 +108,7 @@ export default function ({ getService }: FtrProviderContext) { it('should return a subset of the ancestors', async () => { const { body }: { body: ResolverNode[] } = await supertest .post('/api/endpoint/resolver/tree') - .set('kbn-xsrf', 'xxx') + .set(HEADERS) .send({ descendants: 0, descendantLevels: 0, @@ -130,7 +134,7 @@ export default function ({ getService }: FtrProviderContext) { it('should return ancestors without the ancestry array', async () => { const { body }: { body: ResolverNode[] } = await supertest .post('/api/endpoint/resolver/tree') - .set('kbn-xsrf', 'xxx') + .set(HEADERS) .send({ descendants: 0, descendantLevels: 0, @@ -158,7 +162,7 @@ export default function ({ getService }: FtrProviderContext) { ).toISOString(); const { body }: { body: ResolverNode[] } = await supertest .post('/api/endpoint/resolver/tree') - .set('kbn-xsrf', 'xxx') + .set(HEADERS) .send({ descendants: 0, descendantLevels: 0, @@ -185,7 +189,7 @@ export default function ({ getService }: FtrProviderContext) { const bottomMostDescendant = Array.from(tree.childrenLevels[1].values())[0].id; const { body }: { body: ResolverNode[] } = await supertest .post('/api/endpoint/resolver/tree') - .set('kbn-xsrf', 'xxx') + .set(HEADERS) .send({ descendants: 0, descendantLevels: 0, @@ -221,7 +225,7 @@ export default function ({ getService }: FtrProviderContext) { const rightNode = level0Nodes[2].id; const { body }: { body: ResolverNode[] } = await supertest .post('/api/endpoint/resolver/tree') - .set('kbn-xsrf', 'xxx') + .set(HEADERS) .send({ descendants: 0, descendantLevels: 0, @@ -252,7 +256,7 @@ export default function ({ getService }: FtrProviderContext) { it('should not return any nodes when the search index does not have any data', async () => { const { body }: { body: ResolverNode[] } = await supertest .post('/api/endpoint/resolver/tree') - .set('kbn-xsrf', 'xxx') + .set(HEADERS) .send({ descendants: 0, descendantLevels: 0, @@ -274,7 +278,7 @@ export default function ({ getService }: FtrProviderContext) { it('returns all descendants for the origin without using the ancestry field', async () => { const { body }: { body: ResolverNode[] } = await supertest .post('/api/endpoint/resolver/tree') - .set('kbn-xsrf', 'xxx') + .set(HEADERS) .send({ descendants: 100, descendantLevels: 2, @@ -303,7 +307,7 @@ export default function ({ getService }: FtrProviderContext) { it('returns all descendants for the origin using the ancestry field', async () => { const { body }: { body: ResolverNode[] } = await supertest .post('/api/endpoint/resolver/tree') - .set('kbn-xsrf', 'xxx') + .set(HEADERS) .send({ descendants: 100, // should be ignored when using the ancestry array @@ -333,7 +337,7 @@ export default function ({ getService }: FtrProviderContext) { it('should handle an invalid id', async () => { const { body }: { body: ResolverNode[] } = await supertest .post('/api/endpoint/resolver/tree') - .set('kbn-xsrf', 'xxx') + .set(HEADERS) .send({ descendants: 100, descendantLevels: 100, @@ -356,7 +360,7 @@ export default function ({ getService }: FtrProviderContext) { const childID = Array.from(tree.childrenLevels[0].values())[0].id; const { body }: { body: ResolverNode[] } = await supertest .post('/api/endpoint/resolver/tree') - .set('kbn-xsrf', 'xxx') + .set(HEADERS) .send({ descendants: 100, descendantLevels: 1, @@ -389,7 +393,7 @@ export default function ({ getService }: FtrProviderContext) { const rightNodeID = level0Nodes[2].id; const { body }: { body: ResolverNode[] } = await supertest .post('/api/endpoint/resolver/tree') - .set('kbn-xsrf', 'xxx') + .set(HEADERS) .send({ descendants: 6, descendantLevels: 0, @@ -422,7 +426,7 @@ export default function ({ getService }: FtrProviderContext) { expect(originGrandparent).to.not.be(''); const { body }: { body: ResolverNode[] } = await supertest .post('/api/endpoint/resolver/tree') - .set('kbn-xsrf', 'xxx') + .set(HEADERS) .send({ descendants: 2, descendantLevels: 0, @@ -459,7 +463,7 @@ export default function ({ getService }: FtrProviderContext) { expect(originGrandparent).to.not.be(''); const { body }: { body: ResolverNode[] } = await supertest .post('/api/endpoint/resolver/tree') - .set('kbn-xsrf', 'xxx') + .set(HEADERS) .send({ descendants: 6, descendantLevels: 1, @@ -495,7 +499,7 @@ export default function ({ getService }: FtrProviderContext) { ).toISOString(); const { body }: { body: ResolverNode[] } = await supertest .post('/api/endpoint/resolver/tree') - .set('kbn-xsrf', 'xxx') + .set(HEADERS) .send({ descendants: 100, descendantLevels: 5, @@ -524,7 +528,7 @@ export default function ({ getService }: FtrProviderContext) { it('returns all descendants and ancestors without the ancestry field and they should have the name field', async () => { const { body }: { body: ResolverNode[] } = await supertest .post('/api/endpoint/resolver/tree') - .set('kbn-xsrf', 'xxx') + .set(HEADERS) .send({ descendants: 100, descendantLevels: 10, @@ -562,7 +566,7 @@ export default function ({ getService }: FtrProviderContext) { it('returns all descendants and ancestors without the ancestry field', async () => { const { body }: { body: ResolverNode[] } = await supertest .post('/api/endpoint/resolver/tree') - .set('kbn-xsrf', 'xxx') + .set(HEADERS) .send({ descendants: 100, descendantLevels: 10, @@ -600,7 +604,7 @@ export default function ({ getService }: FtrProviderContext) { it('returns all descendants and ancestors with the ancestry field', async () => { const { body }: { body: ResolverNode[] } = await supertest .post('/api/endpoint/resolver/tree') - .set('kbn-xsrf', 'xxx') + .set(HEADERS) .send({ descendants: 100, descendantLevels: 10, @@ -638,7 +642,7 @@ export default function ({ getService }: FtrProviderContext) { it('returns an empty response when limits are zero', async () => { const { body }: { body: ResolverNode[] } = await supertest .post('/api/endpoint/resolver/tree') - .set('kbn-xsrf', 'xxx') + .set(HEADERS) .send({ descendants: 0, descendantLevels: 0, diff --git a/x-pack/test/security_solution_endpoint_api_int/config.base.ts b/x-pack/test/security_solution_endpoint_api_int/config.base.ts new file mode 100644 index 00000000000000..039030e2a2230b --- /dev/null +++ b/x-pack/test/security_solution_endpoint_api_int/config.base.ts @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Config } from '@kbn/test'; +import { getRegistryUrlAsArray, createEndpointDockerConfig } from './registry'; +import { SUITE_TAGS } from '../security_solution_endpoint/config.base'; + +export const generateConfig = async ({ + baseConfig, + junitReportName, + kbnServerArgs = [], + target, + services, +}: { + baseConfig: Config; + junitReportName: string; + kbnServerArgs?: string[]; + target: keyof typeof SUITE_TAGS; + services: any; +}): Promise => { + return { + ...baseConfig.getAll(), + testFiles: [require.resolve('./apis')], + dockerServers: createEndpointDockerConfig(), + services, + junit: { + reportName: junitReportName, + }, + suiteTags: { + ...baseConfig.get('suiteTags'), + include: [...baseConfig.get('suiteTags.include'), ...SUITE_TAGS[target].include], + exclude: [...baseConfig.get('suiteTags.exclude'), ...SUITE_TAGS[target].exclude], + }, + kbnTestServer: { + ...baseConfig.get('kbnTestServer'), + serverArgs: [ + ...baseConfig.get('kbnTestServer.serverArgs'), + // if you return an empty string here the kibana server will not start properly but an empty array works + ...getRegistryUrlAsArray(), + // always install Endpoint package by default when Fleet sets up + `--xpack.fleet.packages.0.name=endpoint`, + `--xpack.fleet.packages.0.version=latest`, + // this will be removed in 8.7 when the file upload feature is released + `--xpack.fleet.enableExperimental.0=diagnosticFileUploadEnabled`, + // set any experimental feature flags for testing + `--xpack.securitySolution.enableExperimental=${JSON.stringify([])}`, + + ...kbnServerArgs, + ], + }, + }; +}; diff --git a/x-pack/test/security_solution_endpoint_api_int/config.ts b/x-pack/test/security_solution_endpoint_api_int/config.ts index 0f2f245378a3c2..3789004aba4973 100644 --- a/x-pack/test/security_solution_endpoint_api_int/config.ts +++ b/x-pack/test/security_solution_endpoint_api_int/config.ts @@ -6,34 +6,16 @@ */ import { FtrConfigProviderContext } from '@kbn/test'; -import { createEndpointDockerConfig, getRegistryUrlAsArray } from './registry'; +import { generateConfig } from './config.base'; import { services } from './services'; export default async function ({ readConfigFile }: FtrConfigProviderContext) { const xPackAPITestsConfig = await readConfigFile(require.resolve('../api_integration/config.ts')); - return { - ...xPackAPITestsConfig.getAll(), - testFiles: [require.resolve('./apis')], - dockerServers: createEndpointDockerConfig(), + return generateConfig({ + baseConfig: xPackAPITestsConfig, + junitReportName: 'X-Pack Endpoint API Integration Tests against ESS', + target: 'ess', services, - junit: { - reportName: 'X-Pack Endpoint API Integration Tests', - }, - kbnTestServer: { - ...xPackAPITestsConfig.get('kbnTestServer'), - serverArgs: [ - ...xPackAPITestsConfig.get('kbnTestServer.serverArgs'), - // if you return an empty string here the kibana server will not start properly but an empty array works - ...getRegistryUrlAsArray(), - // always install Endpoint package by default when Fleet sets up - `--xpack.fleet.packages.0.name=endpoint`, - `--xpack.fleet.packages.0.version=latest`, - // this will be removed in 8.7 when the file upload feature is released - `--xpack.fleet.enableExperimental.0=diagnosticFileUploadEnabled`, - // set any experimental feature flags for testing - `--xpack.securitySolution.enableExperimental=${JSON.stringify([])}`, - ], - }, - }; + }); } diff --git a/x-pack/test/security_solution_endpoint_api_int/headers.ts b/x-pack/test/security_solution_endpoint_api_int/headers.ts new file mode 100644 index 00000000000000..e4ca6acfbbc654 --- /dev/null +++ b/x-pack/test/security_solution_endpoint_api_int/headers.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const HEADERS = Object.freeze({ + 'kbn-xsrf': 'security_solution', + 'x-elastic-internal-origin': 'security_solution', +}); diff --git a/x-pack/test/security_solution_endpoint_api_int/serverless.config.ts b/x-pack/test/security_solution_endpoint_api_int/serverless.config.ts new file mode 100644 index 00000000000000..262bf4dafa2f72 --- /dev/null +++ b/x-pack/test/security_solution_endpoint_api_int/serverless.config.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 { FtrConfigProviderContext } from '@kbn/test'; +import { generateConfig } from './config.base'; +import { svlServices } from './services'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const serverlessTestsConfig = await readConfigFile( + require.resolve('../../test_serverless/shared/config.base.ts') + ); + + return generateConfig({ + baseConfig: serverlessTestsConfig, + junitReportName: 'X-Pack Endpoint API Integration Tests against Serverless', + target: 'serverless', + kbnServerArgs: ['--serverless=security'], + services: svlServices, + }); +} diff --git a/x-pack/test/security_solution_endpoint_api_int/services/index.ts b/x-pack/test/security_solution_endpoint_api_int/services/index.ts index e94e41f37b9228..44a4354d928f59 100644 --- a/x-pack/test/security_solution_endpoint_api_int/services/index.ts +++ b/x-pack/test/security_solution_endpoint_api_int/services/index.ts @@ -5,6 +5,10 @@ * 2.0. */ +import { + KibanaSupertestWithCertProvider, + KibanaSupertestWithCertWithoutAuthProvider, +} from '../../security_solution_endpoint/services/supertest_with_cert'; import { services as xPackAPIServices } from '../../api_integration/services'; import { ResolverGeneratorProvider } from './resolver'; import { RolesUsersProvider } from './roles_users'; @@ -20,3 +24,10 @@ export const services = { endpointArtifactTestResources: EndpointArtifactsTestResources, rolesUsersProvider: RolesUsersProvider, }; + +export const svlServices = { + ...services, + + supertest: KibanaSupertestWithCertProvider, + supertestWithoutAuth: KibanaSupertestWithCertWithoutAuthProvider, +}; diff --git a/x-pack/test/security_solution_endpoint_api_int/services/roles_users.ts b/x-pack/test/security_solution_endpoint_api_int/services/roles_users.ts index baac39815b4886..1d9c2a353a1f60 100644 --- a/x-pack/test/security_solution_endpoint_api_int/services/roles_users.ts +++ b/x-pack/test/security_solution_endpoint_api_int/services/roles_users.ts @@ -5,49 +5,17 @@ * 2.0. */ -import type { Role } from '@kbn/security-plugin/common'; - -import { getT1Analyst } from '@kbn/security-solution-plugin/scripts/endpoint/common/roles_users/t1_analyst'; -import { getT2Analyst } from '@kbn/security-solution-plugin/scripts/endpoint/common/roles_users/t2_analyst'; -import { getHunter } from '@kbn/security-solution-plugin/scripts/endpoint/common/roles_users/hunter'; -import { getThreatIntelligenceAnalyst } from '@kbn/security-solution-plugin/scripts/endpoint/common/roles_users/threat_intelligence_analyst'; -import { getDetectionsEngineer } from '@kbn/security-solution-plugin/scripts/endpoint/common/roles_users/detections_engineer'; -import { getSocManager } from '@kbn/security-solution-plugin/scripts/endpoint/common/roles_users/soc_manager'; -import { getPlatformEngineer } from '@kbn/security-solution-plugin/scripts/endpoint/common/roles_users/platform_engineer'; -import { getEndpointOperationsAnalyst } from '@kbn/security-solution-plugin/scripts/endpoint/common/roles_users/endpoint_operations_analyst'; -import { getEndpointSecurityPolicyManager } from '@kbn/security-solution-plugin/scripts/endpoint/common/roles_users/endpoint_security_policy_manager'; -import { getWithResponseActionsRole } from '@kbn/security-solution-plugin/scripts/endpoint/common/roles_users/with_response_actions_role'; -import { getWithArtifactReadPrivilegesRole } from '@kbn/security-solution-plugin/scripts/endpoint/common/roles_users/with_artifact_read_privileges_role'; +import { + EndpointSecurityRoleNames, + ENDPOINT_SECURITY_ROLE_NAMES, + getAllEndpointSecurityRoles, +} from '@kbn/security-solution-plugin/scripts/endpoint/common/roles_users'; import { FtrProviderContext } from '../ftr_provider_context'; -export enum ROLE { - t1_analyst = 't1Analyst', - t2_analyst = 't2Analyst', - analyst_hunter = 'hunter', - threat_intelligence_analyst = 'threatIntelligenceAnalyst', - detections_engineer = 'detectionsEngineer', - soc_manager = 'socManager', - platform_engineer = 'platformEngineer', - endpoint_operations_analyst = 'endpointOperationsAnalyst', - endpoint_security_policy_manager = 'endpointSecurityPolicyManager', - response_actions_role = 'executeResponseActions', - artifact_read_role = 'artifactReadRole', -} +export const ROLE = ENDPOINT_SECURITY_ROLE_NAMES; -const rolesMapping: { [key in ROLE]: Omit } = { - t1Analyst: getT1Analyst(), - t2Analyst: getT2Analyst(), - hunter: getHunter(), - threatIntelligenceAnalyst: getThreatIntelligenceAnalyst(), - detectionsEngineer: getDetectionsEngineer(), - socManager: getSocManager(), - platformEngineer: getPlatformEngineer(), - endpointOperationsAnalyst: getEndpointOperationsAnalyst(), - endpointSecurityPolicyManager: getEndpointSecurityPolicyManager(), - executeResponseActions: getWithResponseActionsRole(), - artifactReadRole: getWithArtifactReadPrivilegesRole(), -}; +const rolesMapping = getAllEndpointSecurityRoles(); export function RolesUsersProvider({ getService }: FtrProviderContext) { const security = getService('security'); @@ -76,7 +44,7 @@ export function RolesUsersProvider({ getService }: FtrProviderContext) { * @param options */ async createRole(options: { - predefinedRole?: ROLE; + predefinedRole?: EndpointSecurityRoleNames; extraPrivileges?: string[]; customRole?: { roleName: string; extraPrivileges: string[] }; }): Promise { diff --git a/x-pack/test_serverless/functional/test_suites/common/management/index_management/create_enrich_policy.ts b/x-pack/test_serverless/functional/test_suites/common/management/index_management/create_enrich_policy.ts index b3e248ea82de4a..f5b51c815e0fef 100644 --- a/x-pack/test_serverless/functional/test_suites/common/management/index_management/create_enrich_policy.ts +++ b/x-pack/test_serverless/functional/test_suites/common/management/index_management/create_enrich_policy.ts @@ -20,8 +20,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const POLICY_NAME = `policy-${Math.random()}`; describe('Create enrich policy', function () { - // TimeoutError: Waiting for element to be located By(css selector, [data-test-subj="enrichPoliciesEmptyPromptCreateButton"]) - this.tags(['failsOnMKI']); before(async () => { log.debug('Creating test index'); try { diff --git a/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/flyout_highlights.ts b/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/flyout_highlights.ts new file mode 100644 index 00000000000000..98a3914fed4e8d --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/flyout_highlights.ts @@ -0,0 +1,291 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { FtrProviderContext } from '../../../ftr_provider_context'; + +const DATASET_NAME = 'flyout'; +const NAMESPACE = 'default'; +const DATA_STREAM_NAME = `logs-${DATASET_NAME}-${NAMESPACE}`; +const NOW = Date.now(); + +const sharedDoc = { + time: NOW + 1000, + logFilepath: '/flyout.log', + serviceName: 'frontend-node', + datasetName: DATASET_NAME, + namespace: NAMESPACE, + message: 'full document', + logLevel: 'info', + traceId: 'abcdef', + hostName: 'gke-edge-oblt-pool', + orchestratorClusterId: 'my-cluster-id', + orchestratorClusterName: 'my-cluster-id', + orchestratorResourceId: 'orchestratorResourceId', + cloudProvider: 'gcp', + cloudRegion: 'us-central-1', + cloudAz: 'us-central-1a', + cloudProjectId: 'elastic-project', + cloudInstanceId: 'BgfderflkjTheUiGuy', + agentName: 'node', +}; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const dataGrid = getService('dataGrid'); + const testSubjects = getService('testSubjects'); + const PageObjects = getPageObjects(['observabilityLogExplorer', 'svlCommonPage']); + + describe('Flyout highlight customization', () => { + let cleanupDataStreamSetup: () => Promise; + + describe('Service container', () => { + const { serviceName, traceId, ...rest } = sharedDoc; + const docWithoutServiceName = { ...rest, traceId, time: NOW - 1000 }; + const docWithoutTraceId = { ...rest, serviceName, time: NOW - 2000 }; + const docWithoutServiceContainer = { ...rest, time: NOW - 4000 }; + + const docs = [ + sharedDoc, + docWithoutServiceName, + docWithoutTraceId, + docWithoutServiceContainer, + ]; + before('setup DataStream', async () => { + cleanupDataStreamSetup = await PageObjects.observabilityLogExplorer.setupDataStream( + DATASET_NAME, + NAMESPACE + ); + await PageObjects.observabilityLogExplorer.ingestLogEntries(DATA_STREAM_NAME, docs); + await PageObjects.svlCommonPage.login(); + }); + + after('clean up DataStream', async () => { + await PageObjects.svlCommonPage.forceLogout(); + if (cleanupDataStreamSetup) { + await cleanupDataStreamSetup(); + } + }); + + beforeEach(async () => { + await PageObjects.observabilityLogExplorer.navigateTo({ + from: new Date(NOW - 60_000).toISOString(), + to: new Date(NOW + 60_000).toISOString(), + }); + }); + + it('should load the service container with all fields', async () => { + await dataGrid.clickRowToggle(); + await testSubjects.existOrFail('logExplorerFlyoutHighlightSectionService'); + await testSubjects.existOrFail('logExplorerFlyoutService'); + await testSubjects.existOrFail('logExplorerFlyoutTrace'); + await dataGrid.closeFlyout(); + }); + + it('should load the service container even when 1 field is missing', async () => { + await dataGrid.clickRowToggle({ rowIndex: 1 }); + await testSubjects.existOrFail('logExplorerFlyoutHighlightSectionService'); + await testSubjects.missingOrFail('logExplorerFlyoutService'); + await testSubjects.existOrFail('logExplorerFlyoutTrace'); + await dataGrid.closeFlyout(); + }); + + it('should not load the service container if all fields are missing', async () => { + await dataGrid.clickRowToggle({ rowIndex: 3 }); + await testSubjects.missingOrFail('logExplorerFlyoutHighlightSectionService'); + await testSubjects.missingOrFail('logExplorerFlyoutService'); + await testSubjects.missingOrFail('logExplorerFlyoutTrace'); + await dataGrid.closeFlyout(); + }); + }); + + describe('Infrastructure container', () => { + const { hostName, orchestratorClusterName, orchestratorResourceId, ...rest } = sharedDoc; + const docWithoutHostName = { + ...rest, + orchestratorClusterName, + orchestratorResourceId, + time: NOW - 1000, + }; + const docWithoutInfrastructureContainer = { ...rest, time: NOW - 2000 }; + + const docs = [sharedDoc, docWithoutHostName, docWithoutInfrastructureContainer]; + before('setup DataStream', async () => { + cleanupDataStreamSetup = await PageObjects.observabilityLogExplorer.setupDataStream( + DATASET_NAME, + NAMESPACE + ); + await PageObjects.observabilityLogExplorer.ingestLogEntries(DATA_STREAM_NAME, docs); + await PageObjects.svlCommonPage.login(); + }); + + after('clean up DataStream', async () => { + await PageObjects.svlCommonPage.forceLogout(); + if (cleanupDataStreamSetup) { + await cleanupDataStreamSetup(); + } + }); + + beforeEach(async () => { + await PageObjects.observabilityLogExplorer.navigateTo({ + from: new Date(NOW - 60_000).toISOString(), + to: new Date(NOW + 60_000).toISOString(), + }); + }); + + it('should load the infrastructure container with all fields', async () => { + await dataGrid.clickRowToggle(); + await testSubjects.existOrFail('logExplorerFlyoutHighlightSectionInfrastructure'); + await testSubjects.existOrFail('logExplorerFlyoutHostName'); + await testSubjects.existOrFail('logExplorerFlyoutClusterName'); + await testSubjects.existOrFail('logExplorerFlyoutResourceId'); + await dataGrid.closeFlyout(); + }); + + it('should load the infrastructure container even when 1 field is missing', async () => { + await dataGrid.clickRowToggle({ rowIndex: 1 }); + await testSubjects.existOrFail('logExplorerFlyoutHighlightSectionInfrastructure'); + await testSubjects.missingOrFail('logExplorerFlyoutHostName'); + await testSubjects.existOrFail('logExplorerFlyoutClusterName'); + await testSubjects.existOrFail('logExplorerFlyoutResourceId'); + await dataGrid.closeFlyout(); + }); + + it('should not load the infrastructure container if all fields are missing', async () => { + await dataGrid.clickRowToggle({ rowIndex: 2 }); + await testSubjects.missingOrFail('logExplorerFlyoutHighlightSectionInfrastructure'); + await testSubjects.missingOrFail('logExplorerFlyoutHostName'); + await testSubjects.missingOrFail('logExplorerFlyoutClusterName'); + await testSubjects.missingOrFail('logExplorerFlyoutResourceId'); + await dataGrid.closeFlyout(); + }); + }); + + describe('Cloud container', () => { + const { cloudProvider, cloudInstanceId, cloudProjectId, cloudRegion, cloudAz, ...rest } = + sharedDoc; + const docWithoutCloudProviderAndInstanceId = { + ...rest, + cloudProjectId, + cloudRegion, + cloudAz, + time: NOW - 1000, + }; + const docWithoutCloudContainer = { ...rest, time: NOW - 2000 }; + + const docs = [sharedDoc, docWithoutCloudProviderAndInstanceId, docWithoutCloudContainer]; + before('setup DataStream', async () => { + cleanupDataStreamSetup = await PageObjects.observabilityLogExplorer.setupDataStream( + DATASET_NAME, + NAMESPACE + ); + await PageObjects.observabilityLogExplorer.ingestLogEntries(DATA_STREAM_NAME, docs); + await PageObjects.svlCommonPage.login(); + }); + + after('clean up DataStream', async () => { + await PageObjects.svlCommonPage.forceLogout(); + if (cleanupDataStreamSetup) { + await cleanupDataStreamSetup(); + } + }); + + beforeEach(async () => { + await PageObjects.observabilityLogExplorer.navigateTo({ + from: new Date(NOW - 60_000).toISOString(), + to: new Date(NOW + 60_000).toISOString(), + }); + }); + + it('should load the cloud container with all fields', async () => { + await dataGrid.clickRowToggle(); + await testSubjects.existOrFail('logExplorerFlyoutHighlightSectionCloud'); + await testSubjects.existOrFail('logExplorerFlyoutCloudProvider'); + await testSubjects.existOrFail('logExplorerFlyoutCloudRegion'); + await testSubjects.existOrFail('logExplorerFlyoutCloudAz'); + await testSubjects.existOrFail('logExplorerFlyoutCloudProjectId'); + await testSubjects.existOrFail('logExplorerFlyoutCloudInstanceId'); + await dataGrid.closeFlyout(); + }); + + it('should load the cloud container even when some fields are missing', async () => { + await dataGrid.clickRowToggle({ rowIndex: 1 }); + await testSubjects.existOrFail('logExplorerFlyoutHighlightSectionCloud'); + + await testSubjects.missingOrFail('logExplorerFlyoutCloudProvider'); + await testSubjects.missingOrFail('logExplorerFlyoutCloudInstanceId'); + + await testSubjects.existOrFail('logExplorerFlyoutCloudRegion'); + await testSubjects.existOrFail('logExplorerFlyoutCloudAz'); + await testSubjects.existOrFail('logExplorerFlyoutCloudProjectId'); + await dataGrid.closeFlyout(); + }); + + it('should not load the cloud container if all fields are missing', async () => { + await dataGrid.clickRowToggle({ rowIndex: 2 }); + await testSubjects.missingOrFail('logExplorerFlyoutHighlightSectionCloud'); + await testSubjects.missingOrFail('logExplorerFlyoutCloudProvider'); + await testSubjects.missingOrFail('logExplorerFlyoutCloudRegion'); + await testSubjects.missingOrFail('logExplorerFlyoutCloudAz'); + await testSubjects.missingOrFail('logExplorerFlyoutCloudProjectId'); + await testSubjects.missingOrFail('logExplorerFlyoutCloudInstanceId'); + await dataGrid.closeFlyout(); + }); + }); + + describe('Other container', () => { + const { logFilepath, agentName, ...rest } = sharedDoc; + const docWithoutLogPathAndAgentName = { + ...rest, + time: NOW - 1000, + }; + + const docs = [sharedDoc, docWithoutLogPathAndAgentName]; + before('setup DataStream', async () => { + cleanupDataStreamSetup = await PageObjects.observabilityLogExplorer.setupDataStream( + DATASET_NAME, + NAMESPACE + ); + await PageObjects.observabilityLogExplorer.ingestLogEntries(DATA_STREAM_NAME, docs); + await PageObjects.svlCommonPage.login(); + }); + + after('clean up DataStream', async () => { + await PageObjects.svlCommonPage.forceLogout(); + if (cleanupDataStreamSetup) { + await cleanupDataStreamSetup(); + } + }); + + beforeEach(async () => { + await PageObjects.observabilityLogExplorer.navigateTo({ + from: new Date(NOW - 60_000).toISOString(), + to: new Date(NOW + 60_000).toISOString(), + }); + }); + + it('should load the other container with all fields', async () => { + await dataGrid.clickRowToggle(); + await testSubjects.existOrFail('logExplorerFlyoutHighlightSectionOther'); + await testSubjects.existOrFail('logExplorerFlyoutLogPathFile'); + await testSubjects.existOrFail('logExplorerFlyoutNamespace'); + await testSubjects.existOrFail('logExplorerFlyoutDataset'); + await testSubjects.existOrFail('logExplorerFlyoutLogShipper'); + await dataGrid.closeFlyout(); + }); + + it('should load the other container even when some fields are missing', async () => { + await dataGrid.clickRowToggle({ rowIndex: 1 }); + await testSubjects.existOrFail('logExplorerFlyoutHighlightSectionOther'); + + await testSubjects.missingOrFail('logExplorerFlyoutLogPathFile'); + await testSubjects.missingOrFail('logExplorerFlyoutLogShipper'); + + await testSubjects.existOrFail('logExplorerFlyoutNamespace'); + await testSubjects.existOrFail('logExplorerFlyoutDataset'); + await dataGrid.closeFlyout(); + }); + }); + }); +} diff --git a/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/index.ts b/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/index.ts index 6e0b6508696809..9f3d9b7e39c86a 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/index.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/observability_log_explorer/index.ts @@ -16,5 +16,7 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./filter_controls')); loadTestFile(require.resolve('./flyout')); loadTestFile(require.resolve('./header_menu')); + loadTestFile(require.resolve('./header_menu')); + loadTestFile(require.resolve('./flyout_highlights.ts')); }); } diff --git a/x-pack/test_serverless/functional/test_suites/search/navigation.ts b/x-pack/test_serverless/functional/test_suites/search/navigation.ts index 03f229f21b47a3..8796dcfe937730 100644 --- a/x-pack/test_serverless/functional/test_suites/search/navigation.ts +++ b/x-pack/test_serverless/functional/test_suites/search/navigation.ts @@ -72,20 +72,16 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { it("management apps from the sidenav hide the 'stack management' root from the breadcrumbs", async () => { await svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'management:triggersActions' }); - await svlCommonNavigation.breadcrumbs.expectBreadcrumbTexts(['Explore', 'Alerts', 'Rules']); + await svlCommonNavigation.breadcrumbs.expectBreadcrumbTexts(['Alerts', 'Rules']); await svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'management:index_management' }); - await svlCommonNavigation.breadcrumbs.expectBreadcrumbTexts([ - 'Content', - 'Index Management', - 'Indices', - ]); + await svlCommonNavigation.breadcrumbs.expectBreadcrumbTexts(['Index Management', 'Indices']); await svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'management:ingest_pipelines' }); - await svlCommonNavigation.breadcrumbs.expectBreadcrumbTexts(['Content', 'Ingest Pipelines']); + await svlCommonNavigation.breadcrumbs.expectBreadcrumbTexts(['Ingest Pipelines']); await svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'management:api_keys' }); - await svlCommonNavigation.breadcrumbs.expectBreadcrumbTexts(['Security', 'API keys']); + await svlCommonNavigation.breadcrumbs.expectBreadcrumbTexts(['API keys']); }); it('navigate management', async () => { diff --git a/yarn.lock b/yarn.lock index 0d11dd72a03596..dbf3facc5ec104 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1574,10 +1574,10 @@ "@elastic/transport" "^8.3.3" tslib "^2.4.0" -"@elastic/ems-client@8.5.0": - version "8.5.0" - resolved "https://registry.yarnpkg.com/@elastic/ems-client/-/ems-client-8.5.0.tgz#5e3988ab01dee54bace9f8c2f9e71470b26a1bfa" - integrity sha512-uEI8teyS3gWErlEL4mEYh0MDHms9Rp3eEHKnMMSdMtAkaa3xs60SOm3Vd0ld9sIPsyarQHrAybAw30GXVXXRjw== +"@elastic/ems-client@8.5.1": + version "8.5.1" + resolved "https://registry.yarnpkg.com/@elastic/ems-client/-/ems-client-8.5.1.tgz#60c179e53ad81f5e26680e7e1c6cbffe4fb64aed" + integrity sha512-lc65i/cW6fGuRMt3pUte35sL8HxXM7TbYJwx1bHWcWn4aN3zv8i5RhPKFz+Wr/1ubfFOTrIoFYCP4h1uq2O/dQ== dependencies: "@types/geojson" "^7946.0.10" "@types/lru-cache" "^5.1.0" @@ -4148,6 +4148,10 @@ version "0.0.0" uid "" +"@kbn/dataset-quality-plugin@link:x-pack/plugins/dataset_quality": + version "0.0.0" + uid "" + "@kbn/datemath@link:packages/kbn-datemath": version "0.0.0" uid "" @@ -4304,6 +4308,10 @@ version "0.0.0" uid "" +"@kbn/error-boundary-example-plugin@link:examples/error_boundary": + version "0.0.0" + uid "" + "@kbn/es-archiver@link:packages/kbn-es-archiver": version "0.0.0" uid "" @@ -8251,7 +8259,7 @@ resolved "https://registry.yarnpkg.com/@testim/chrome-version/-/chrome-version-1.1.3.tgz#fbb68696899d7b8c1b9b891eded9c04fe2cd5529" integrity sha512-g697J3WxV/Zytemz8aTuKjTGYtta9+02kva3C1xc7KXB8GdbfE1akGJIsZLyY/FSh2QrnE+fiB7vmWU3XNcb6A== -"@testing-library/dom@^8.0.0", "@testing-library/dom@^8.19.0": +"@testing-library/dom@^8.0.0": version "8.19.0" resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.19.0.tgz#bd3f83c217ebac16694329e413d9ad5fdcfd785f" integrity sha512-6YWYPPpxG3e/xOo6HIWwB/58HukkwIVTOaZ0VwdMVjhRUX/01E4FtQbck9GazOOj7MXHc5RBzMrU86iBJHbI+A== @@ -20473,10 +20481,10 @@ kdbush@^4.0.2: resolved "https://registry.yarnpkg.com/kdbush/-/kdbush-4.0.2.tgz#2f7b7246328b4657dd122b6c7f025fbc2c868e39" integrity sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA== -kea@^2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/kea/-/kea-2.4.2.tgz#53af42702f2c8962422e456e5dd943391bad26e9" - integrity sha512-cdGds/gsJsbo/KbVAMk5/tTr229eDibVT1wmPPxPO/10zYb8GFoP3udBIQb+Hop5qGEu2wIHVdXwJvXqSS8JAg== +kea@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/kea/-/kea-2.6.0.tgz#774a82188e0fb52cdb18b72843a875ee857f3807" + integrity sha512-+yaLyZx8h2v96aL01XIRZjqA8Qk4fIUziznSKnkjDItUU8YnH75xER6+vMHT5EHC3MJeSScxIx5UuqZl30DBdg== keyv@^3.0.0: version "3.0.0"